diff --git a/0.12/404.html b/0.12/404.html index bf2f01ff1..55ee4d2d9 100644 --- a/0.12/404.html +++ b/0.12/404.html @@ -12,7 +12,7 @@ - + @@ -20,7 +20,7 @@ - + @@ -884,7 +884,7 @@
- +
GitHub diff --git a/0.12/assets/stylesheets/main.3cba04c6.min.css b/0.12/assets/stylesheets/main.3cba04c6.min.css new file mode 100644 index 000000000..873f8feef --- /dev/null +++ b/0.12/assets/stylesheets/main.3cba04c6.min.css @@ -0,0 +1 @@ +@charset "UTF-8";html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;text-size-adjust:none;box-sizing:border-box}*,:after,:before{box-sizing:inherit}@media (prefers-reduced-motion){*,:after,:before{transition:none!important}}body{margin:0}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}hr{border:0;box-sizing:initial;display:block;height:.05rem;overflow:visible;padding:0}small{font-size:80%}sub,sup{line-height:1em}img{border-style:none}table{border-collapse:initial;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{background:#0000;border:0;font-family:inherit;font-size:inherit;margin:0;padding:0}input{border:0;outline:none}:root{--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3;--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:#526cfe1a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-scheme=default]{color-scheme:light}[data-md-color-scheme=default] img[src$="#gh-dark-mode-only"],[data-md-color-scheme=default] img[src$="#only-dark"]{display:none}:root,[data-md-color-scheme=default]{--md-hue:225deg;--md-default-fg-color:#000000de;--md-default-fg-color--light:#0000008a;--md-default-fg-color--lighter:#00000052;--md-default-fg-color--lightest:#00000012;--md-default-bg-color:#fff;--md-default-bg-color--light:#ffffffb3;--md-default-bg-color--lighter:#ffffff4d;--md-default-bg-color--lightest:#ffffff1f;--md-code-fg-color:#36464e;--md-code-bg-color:#f5f5f5;--md-code-hl-color:#4287ff;--md-code-hl-color--light:#4287ff1a;--md-code-hl-number-color:#d52a2a;--md-code-hl-special-color:#db1457;--md-code-hl-function-color:#a846b9;--md-code-hl-constant-color:#6e59d9;--md-code-hl-keyword-color:#3f6ec6;--md-code-hl-string-color:#1c7d4d;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-del-color:#f5503d26;--md-typeset-ins-color:#0bd57026;--md-typeset-kbd-color:#fafafa;--md-typeset-kbd-accent-color:#fff;--md-typeset-kbd-border-color:#b8b8b8;--md-typeset-mark-color:#ffff0080;--md-typeset-table-color:#0000001f;--md-typeset-table-color--light:rgba(0,0,0,.035);--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-warning-fg-color:#000000de;--md-warning-bg-color:#ff9;--md-footer-fg-color:#fff;--md-footer-fg-color--light:#ffffffb3;--md-footer-fg-color--lighter:#ffffff73;--md-footer-bg-color:#000000de;--md-footer-bg-color--dark:#00000052;--md-shadow-z1:0 0.2rem 0.5rem #0000000d,0 0 0.05rem #0000001a;--md-shadow-z2:0 0.2rem 0.5rem #0000001a,0 0 0.05rem #00000040;--md-shadow-z3:0 0.2rem 0.5rem #0003,0 0 0.05rem #00000059}.md-icon svg{fill:currentcolor;display:block;height:1.2rem;width:1.2rem}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--md-text-font-family:var(--md-text-font,_),-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;--md-code-font-family:var(--md-code-font,_),SFMono-Regular,Consolas,Menlo,monospace}aside,body,input{font-feature-settings:"kern","liga";color:var(--md-typeset-color);font-family:var(--md-text-font-family)}code,kbd,pre{font-feature-settings:"kern";font-family:var(--md-code-font-family)}:root{--md-typeset-table-sort-icon:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--asc:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--desc:url('data:image/svg+xml;charset=utf-8,')}.md-typeset{-webkit-print-color-adjust:exact;color-adjust:exact;font-size:.8rem;line-height:1.6}@media print{.md-typeset{font-size:.68rem}}.md-typeset blockquote,.md-typeset dl,.md-typeset figure,.md-typeset ol,.md-typeset pre,.md-typeset ul{margin-bottom:1em;margin-top:1em}.md-typeset h1{color:var(--md-default-fg-color--light);font-size:2em;line-height:1.3;margin:0 0 1.25em}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{font-size:1.5625em;line-height:1.4;margin:1.6em 0 .64em}.md-typeset h3{font-size:1.25em;font-weight:400;letter-spacing:-.01em;line-height:1.5;margin:1.6em 0 .8em}.md-typeset h2+h3{margin-top:.8em}.md-typeset h4{font-weight:700;letter-spacing:-.01em;margin:1em 0}.md-typeset h5,.md-typeset h6{color:var(--md-default-fg-color--light);font-size:.8em;font-weight:700;letter-spacing:-.01em;margin:1.25em 0}.md-typeset h5{text-transform:uppercase}.md-typeset hr{border-bottom:.05rem solid var(--md-default-fg-color--lightest);display:flow-root;margin:1.5em 0}.md-typeset a{color:var(--md-typeset-a-color);word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color 125ms}.md-typeset a:focus,.md-typeset a:hover{color:var(--md-accent-fg-color)}.md-typeset a:focus code,.md-typeset a:hover code{background-color:var(--md-accent-fg-color--transparent)}.md-typeset a code{color:currentcolor;transition:background-color 125ms}.md-typeset a.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset code,.md-typeset kbd,.md-typeset pre{color:var(--md-code-fg-color);direction:ltr;font-variant-ligatures:none}@media print{.md-typeset code,.md-typeset kbd,.md-typeset pre{white-space:pre-wrap}}.md-typeset code{background-color:var(--md-code-bg-color);border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone;font-size:.85em;padding:0 .2941176471em;word-break:break-word}.md-typeset code:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-typeset pre{display:flow-root;line-height:1.4;position:relative}.md-typeset pre>code{-webkit-box-decoration-break:slice;box-decoration-break:slice;box-shadow:none;display:block;margin:0;outline-color:var(--md-accent-fg-color);overflow:auto;padding:.7720588235em 1.1764705882em;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin;touch-action:auto;word-break:normal}.md-typeset pre>code:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-typeset pre>code::-webkit-scrollbar{height:.2rem;width:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}.md-typeset kbd{background-color:var(--md-typeset-kbd-color);border-radius:.1rem;box-shadow:0 .1rem 0 .05rem var(--md-typeset-kbd-border-color),0 .1rem 0 var(--md-typeset-kbd-border-color),0 -.1rem .2rem var(--md-typeset-kbd-accent-color) inset;color:var(--md-default-fg-color);display:inline-block;font-size:.75em;padding:0 .6666666667em;vertical-align:text-top;word-break:break-word}.md-typeset mark{background-color:var(--md-typeset-mark-color);-webkit-box-decoration-break:clone;box-decoration-break:clone;color:inherit;word-break:break-word}.md-typeset abbr{border-bottom:.05rem dotted var(--md-default-fg-color--light);cursor:help;text-decoration:none}.md-typeset small{opacity:.75}[dir=ltr] .md-typeset sub,[dir=ltr] .md-typeset sup{margin-left:.078125em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.078125em}[dir=ltr] .md-typeset blockquote{padding-left:.6rem}[dir=rtl] .md-typeset blockquote{padding-right:.6rem}[dir=ltr] .md-typeset blockquote{border-left:.2rem solid var(--md-default-fg-color--lighter)}[dir=rtl] .md-typeset blockquote{border-right:.2rem solid var(--md-default-fg-color--lighter)}.md-typeset blockquote{color:var(--md-default-fg-color--light);margin-left:0;margin-right:0}.md-typeset ul{list-style-type:disc}[dir=ltr] .md-typeset ol,[dir=ltr] .md-typeset ul{margin-left:.625em}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em}.md-typeset ol,.md-typeset ul{padding:0}.md-typeset ol:not([hidden]),.md-typeset ul:not([hidden]){display:flow-root}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}[dir=ltr] .md-typeset ol li,[dir=ltr] .md-typeset ul li{margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}[dir=ltr] .md-typeset ol li ol,[dir=ltr] .md-typeset ol li ul,[dir=ltr] .md-typeset ul li ol,[dir=ltr] .md-typeset ul li ul{margin-left:.625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin-bottom:.5em;margin-top:.5em}[dir=ltr] .md-typeset dd{margin-left:1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em}.md-typeset dd{margin-bottom:1.5em;margin-top:1em}.md-typeset img,.md-typeset svg,.md-typeset video{height:auto;max-width:100%}.md-typeset img[align=left]{margin:1em 1em 1em 0}.md-typeset img[align=right]{margin:1em 0 1em 1em}.md-typeset img[align]:only-child{margin-top:0}.md-typeset figure{display:flow-root;margin:1em auto;max-width:100%;text-align:center;width:-moz-fit-content;width:fit-content}.md-typeset figure img{display:block;margin:0 auto}.md-typeset figcaption{font-style:italic;margin:1em auto;max-width:24rem}.md-typeset iframe{max-width:100%}.md-typeset table:not([class]){background-color:var(--md-default-bg-color);border:.05rem solid var(--md-typeset-table-color);border-radius:.1rem;display:inline-block;font-size:.64rem;max-width:100%;overflow:auto;touch-action:auto}@media print{.md-typeset table:not([class]){display:table}}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td>:first-child,.md-typeset table:not([class]) th>:first-child{margin-top:0}.md-typeset table:not([class]) td>:last-child,.md-typeset table:not([class]) th>:last-child{margin-bottom:0}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{font-weight:700;min-width:5rem;padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) td{border-top:.05rem solid var(--md-typeset-table-color);padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) tbody tr{transition:background-color 125ms}.md-typeset table:not([class]) tbody tr:hover{background-color:var(--md-typeset-table-color--light);box-shadow:0 .05rem 0 var(--md-default-bg-color) inset}.md-typeset table:not([class]) a{word-break:normal}.md-typeset table th[role=columnheader]{cursor:pointer}[dir=ltr] .md-typeset table th[role=columnheader]:after{margin-left:.5em}[dir=rtl] .md-typeset table th[role=columnheader]:after{margin-right:.5em}.md-typeset table th[role=columnheader]:after{content:"";display:inline-block;height:1.2em;-webkit-mask-image:var(--md-typeset-table-sort-icon);mask-image:var(--md-typeset-table-sort-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color 125ms;vertical-align:text-bottom;width:1.2em}.md-typeset table th[role=columnheader]:hover:after{background-color:var(--md-default-fg-color--lighter)}.md-typeset table th[role=columnheader][aria-sort=ascending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--asc);mask-image:var(--md-typeset-table-sort-icon--asc)}.md-typeset table th[role=columnheader][aria-sort=descending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--desc);mask-image:var(--md-typeset-table-sort-icon--desc)}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;touch-action:auto}.md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}@media print{.md-typeset__table{display:block}}html .md-typeset__table table{display:table;margin:0;overflow:hidden;width:100%}@media screen and (max-width:44.984375em){.md-content__inner>pre{margin:1em -.8rem}.md-content__inner>pre code{border-radius:0}}.md-typeset .md-author{border-radius:100%;display:block;flex-shrink:0;height:1.6rem;overflow:hidden;position:relative;transition:color 125ms,transform 125ms;width:1.6rem}.md-typeset .md-author img{display:block}.md-typeset .md-author--more{background:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--lighter);font-size:.6rem;font-weight:700;line-height:1.6rem;text-align:center}.md-typeset .md-author--long{height:2.4rem;width:2.4rem}.md-typeset a.md-author{transform:scale(1)}.md-typeset a.md-author img{border-radius:100%;filter:grayscale(100%) opacity(75%);transition:filter 125ms}.md-typeset a.md-author:focus,.md-typeset a.md-author:hover{transform:scale(1.1);z-index:1}.md-typeset a.md-author:focus img,.md-typeset a.md-author:hover img{filter:grayscale(0)}.md-banner{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color);overflow:auto}@media print{.md-banner{display:none}}.md-banner--warning{background-color:var(--md-warning-bg-color);color:var(--md-warning-fg-color)}.md-banner__inner{font-size:.7rem;margin:.6rem auto;padding:0 .8rem}[dir=ltr] .md-banner__button{float:right}[dir=rtl] .md-banner__button{float:left}.md-banner__button{color:inherit;cursor:pointer;transition:opacity .25s}.no-js .md-banner__button{display:none}.md-banner__button:hover{opacity:.7}html{font-size:125%;height:100%;overflow-x:hidden}@media screen and (min-width:100em){html{font-size:137.5%}}@media screen and (min-width:125em){html{font-size:150%}}body{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;font-size:.5rem;min-height:100%;position:relative;width:100%}@media print{body{display:block}}@media screen and (max-width:59.984375em){body[data-md-scrolllock]{position:fixed}}.md-grid{margin-left:auto;margin-right:auto;max-width:61rem}.md-container{display:flex;flex-direction:column;flex-grow:1}@media print{.md-container{display:block}}.md-main{flex-grow:1}.md-main__inner{display:flex;height:100%;margin-top:1.5rem}.md-ellipsis{overflow:hidden;text-overflow:ellipsis}.md-toggle{display:none}.md-option{height:0;opacity:0;position:absolute;width:0}.md-option:checked+label:not([hidden]){display:block}.md-option.focus-visible+label{outline-color:var(--md-accent-fg-color);outline-style:auto}.md-skip{background-color:var(--md-default-fg-color);border-radius:.1rem;color:var(--md-default-bg-color);font-size:.64rem;margin:.5rem;opacity:0;outline-color:var(--md-accent-fg-color);padding:.3rem .5rem;position:fixed;transform:translateY(.4rem);z-index:-1}.md-skip:focus{opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 175ms 75ms;z-index:10}@page{margin:25mm}:root{--md-clipboard-icon:url('data:image/svg+xml;charset=utf-8,')}.md-clipboard{border-radius:.1rem;color:var(--md-default-fg-color--lightest);cursor:pointer;height:1.5em;outline-color:var(--md-accent-fg-color);outline-offset:.1rem;position:absolute;right:.5em;top:.5em;transition:color .25s;width:1.5em;z-index:1}@media print{.md-clipboard{display:none}}.md-clipboard:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}:hover>.md-clipboard{color:var(--md-default-fg-color--light)}.md-clipboard:focus,.md-clipboard:hover{color:var(--md-accent-fg-color)}.md-clipboard:after{background-color:currentcolor;content:"";display:block;height:1.125em;margin:0 auto;-webkit-mask-image:var(--md-clipboard-icon);mask-image:var(--md-clipboard-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:1.125em}.md-clipboard--inline{cursor:pointer}.md-clipboard--inline code{transition:color .25s,background-color .25s}.md-clipboard--inline:focus code,.md-clipboard--inline:hover code{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-typeset .md-code__content{display:grid}@keyframes consent{0%{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}@keyframes overlay{0%{opacity:0}to{opacity:1}}.md-consent__overlay{animation:overlay .25s both;-webkit-backdrop-filter:blur(.1rem);backdrop-filter:blur(.1rem);background-color:#0000008a;height:100%;opacity:1;position:fixed;top:0;width:100%;z-index:5}.md-consent__inner{animation:consent .5s cubic-bezier(.1,.7,.1,1) both;background-color:var(--md-default-bg-color);border:0;border-radius:.1rem;bottom:0;box-shadow:0 0 .2rem #0000001a,0 .2rem .4rem #0003;max-height:100%;overflow:auto;padding:0;position:fixed;width:100%;z-index:5}.md-consent__form{padding:.8rem}.md-consent__settings{display:none;margin:1em 0}input:checked+.md-consent__settings{display:block}.md-consent__controls{margin-bottom:.8rem}.md-typeset .md-consent__controls .md-button{display:inline}@media screen and (max-width:44.984375em){.md-typeset .md-consent__controls .md-button{display:block;margin-top:.4rem;text-align:center;width:100%}}.md-consent label{cursor:pointer}.md-content{flex-grow:1;min-width:0}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}@media screen and (min-width:76.25em){[dir=ltr] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}[dir=ltr] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner,[dir=rtl] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem}[dir=rtl] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}}.md-content__inner:before{content:"";display:block;height:.4rem}.md-content__inner>:last-child{margin-bottom:0}[dir=ltr] .md-content__button{float:right}[dir=rtl] .md-content__button{float:left}[dir=ltr] .md-content__button{margin-left:.4rem}[dir=rtl] .md-content__button{margin-right:.4rem}.md-content__button{margin:.4rem 0;padding:0}@media print{.md-content__button{display:none}}.md-typeset .md-content__button{color:var(--md-default-fg-color--lighter)}.md-content__button svg{display:inline;vertical-align:top}[dir=rtl] .md-content__button svg{transform:scaleX(-1)}[dir=ltr] .md-dialog{right:.8rem}[dir=rtl] .md-dialog{left:.8rem}.md-dialog{background-color:var(--md-default-fg-color);border-radius:.1rem;bottom:.8rem;box-shadow:var(--md-shadow-z3);min-width:11.1rem;opacity:0;padding:.4rem .6rem;pointer-events:none;position:fixed;transform:translateY(100%);transition:transform 0ms .4s,opacity .4s;z-index:4}@media print{.md-dialog{display:none}}.md-dialog--active{opacity:1;pointer-events:auto;transform:translateY(0);transition:transform .4s cubic-bezier(.075,.85,.175,1),opacity .4s}.md-dialog__inner{color:var(--md-default-bg-color);font-size:.7rem}.md-feedback{margin:2em 0 1em;text-align:center}.md-feedback fieldset{border:none;margin:0;padding:0}.md-feedback__title{font-weight:700;margin:1em auto}.md-feedback__inner{position:relative}.md-feedback__list{display:flex;flex-wrap:wrap;place-content:baseline center;position:relative}.md-feedback__list:hover .md-icon:not(:disabled){color:var(--md-default-fg-color--lighter)}:disabled .md-feedback__list{min-height:1.8rem}.md-feedback__icon{color:var(--md-default-fg-color--light);cursor:pointer;flex-shrink:0;margin:0 .1rem;transition:color 125ms}.md-feedback__icon:not(:disabled).md-icon:hover{color:var(--md-accent-fg-color)}.md-feedback__icon:disabled{color:var(--md-default-fg-color--lightest);pointer-events:none}.md-feedback__note{opacity:0;position:relative;transform:translateY(.4rem);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-feedback__note>*{margin:0 auto;max-width:16rem}:disabled .md-feedback__note{opacity:1;transform:translateY(0)}.md-footer{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color)}@media print{.md-footer{display:none}}.md-footer__inner{justify-content:space-between;overflow:auto;padding:.2rem}.md-footer__inner:not([hidden]){display:flex}.md-footer__link{align-items:end;display:flex;flex-grow:0.01;margin-bottom:.4rem;margin-top:1rem;max-width:100%;outline-color:var(--md-accent-fg-color);overflow:hidden;transition:opacity .25s}.md-footer__link:focus,.md-footer__link:hover{opacity:.7}[dir=rtl] .md-footer__link svg{transform:scaleX(-1)}@media screen and (max-width:44.984375em){.md-footer__link--prev{flex-shrink:0}.md-footer__link--prev .md-footer__title{display:none}}[dir=ltr] .md-footer__link--next{margin-left:auto}[dir=rtl] .md-footer__link--next{margin-right:auto}.md-footer__link--next{text-align:right}[dir=rtl] .md-footer__link--next{text-align:left}.md-footer__title{flex-grow:1;font-size:.9rem;margin-bottom:.7rem;max-width:calc(100% - 2.4rem);padding:0 1rem;white-space:nowrap}.md-footer__button{margin:.2rem;padding:.4rem}.md-footer__direction{font-size:.64rem;opacity:.7}.md-footer-meta{background-color:var(--md-footer-bg-color--dark)}.md-footer-meta__inner{display:flex;flex-wrap:wrap;justify-content:space-between;padding:.2rem}html .md-footer-meta.md-typeset a{color:var(--md-footer-fg-color--light)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:var(--md-footer-fg-color)}.md-copyright{color:var(--md-footer-fg-color--lighter);font-size:.64rem;margin:auto .6rem;padding:.4rem 0;width:100%}@media screen and (min-width:45em){.md-copyright{width:auto}}.md-copyright__highlight{color:var(--md-footer-fg-color--light)}.md-social{display:inline-flex;gap:.2rem;margin:0 .4rem;padding:.2rem 0 .6rem}@media screen and (min-width:45em){.md-social{padding:.6rem 0}}.md-social__link{display:inline-block;height:1.6rem;text-align:center;width:1.6rem}.md-social__link:before{line-height:1.9}.md-social__link svg{fill:currentcolor;max-height:.8rem;vertical-align:-25%}.md-typeset .md-button{border:.1rem solid;border-radius:.1rem;color:var(--md-primary-fg-color);cursor:pointer;display:inline-block;font-weight:700;padding:.625em 2em;transition:color 125ms,background-color 125ms,border-color 125ms}.md-typeset .md-button--primary{background-color:var(--md-primary-fg-color);border-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color)}.md-typeset .md-button:focus,.md-typeset .md-button:hover{background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[dir=ltr] .md-typeset .md-input{border-top-left-radius:.1rem}[dir=ltr] .md-typeset .md-input,[dir=rtl] .md-typeset .md-input{border-top-right-radius:.1rem}[dir=rtl] .md-typeset .md-input{border-top-left-radius:.1rem}.md-typeset .md-input{border-bottom:.1rem solid var(--md-default-fg-color--lighter);box-shadow:var(--md-shadow-z1);font-size:.8rem;height:1.8rem;padding:0 .6rem;transition:border .25s,box-shadow .25s}.md-typeset .md-input:focus,.md-typeset .md-input:hover{border-bottom-color:var(--md-accent-fg-color);box-shadow:var(--md-shadow-z2)}.md-typeset .md-input--stretch{width:100%}.md-header{background-color:var(--md-primary-fg-color);box-shadow:0 0 .2rem #0000,0 .2rem .4rem #0000;color:var(--md-primary-bg-color);display:block;left:0;position:sticky;right:0;top:0;z-index:4}@media print{.md-header{display:none}}.md-header[hidden]{transform:translateY(-100%);transition:transform .25s cubic-bezier(.8,0,.6,1),box-shadow .25s}.md-header--shadow{box-shadow:0 0 .2rem #0000001a,0 .2rem .4rem #0003;transition:transform .25s cubic-bezier(.1,.7,.1,1),box-shadow .25s}.md-header__inner{align-items:center;display:flex;padding:0 .2rem}.md-header__button{color:currentcolor;cursor:pointer;margin:.2rem;outline-color:var(--md-accent-fg-color);padding:.4rem;position:relative;transition:opacity .25s;vertical-align:middle;z-index:1}.md-header__button:hover{opacity:.7}.md-header__button:not([hidden]){display:inline-block}.md-header__button:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-header__button.md-logo{margin:.2rem;padding:.4rem}@media screen and (max-width:76.234375em){.md-header__button.md-logo{display:none}}.md-header__button.md-logo img,.md-header__button.md-logo svg{fill:currentcolor;display:block;height:1.2rem;width:auto}@media screen and (min-width:60em){.md-header__button[for=__search]{display:none}}.no-js .md-header__button[for=__search]{display:none}[dir=rtl] .md-header__button[for=__search] svg{transform:scaleX(-1)}@media screen and (min-width:76.25em){.md-header__button[for=__drawer]{display:none}}.md-header__topic{display:flex;max-width:100%;position:absolute;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;white-space:nowrap}.md-header__topic+.md-header__topic{opacity:0;pointer-events:none;transform:translateX(1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__topic+.md-header__topic{transform:translateX(-1.25rem)}.md-header__topic:first-child{font-weight:700}[dir=ltr] .md-header__title{margin-left:1rem;margin-right:.4rem}[dir=rtl] .md-header__title{margin-left:.4rem;margin-right:1rem}.md-header__title{flex-grow:1;font-size:.9rem;height:2.4rem;line-height:2.4rem}.md-header__title--active .md-header__topic{opacity:0;pointer-events:none;transform:translateX(-1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__title--active .md-header__topic{transform:translateX(1.25rem)}.md-header__title--active .md-header__topic+.md-header__topic{opacity:1;pointer-events:auto;transform:translateX(0);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;z-index:0}.md-header__title>.md-header__ellipsis{height:100%;position:relative;width:100%}.md-header__option{display:flex;flex-shrink:0;max-width:100%;transition:max-width 0ms .25s,opacity .25s .25s;white-space:nowrap}[data-md-toggle=search]:checked~.md-header .md-header__option{max-width:0;opacity:0;transition:max-width 0ms,opacity 0ms}.md-header__option>input{bottom:0}.md-header__source{display:none}@media screen and (min-width:60em){[dir=ltr] .md-header__source{margin-left:1rem}[dir=rtl] .md-header__source{margin-right:1rem}.md-header__source{display:block;max-width:11.7rem;width:11.7rem}}@media screen and (min-width:76.25em){[dir=ltr] .md-header__source{margin-left:1.4rem}[dir=rtl] .md-header__source{margin-right:1.4rem}}.md-meta{color:var(--md-default-fg-color--light);font-size:.7rem;line-height:1.3}.md-meta__list{display:inline-flex;flex-wrap:wrap;list-style:none;margin:0;padding:0}.md-meta__item:not(:last-child):after{content:"ยท";margin-left:.2rem;margin-right:.2rem}.md-meta__link{color:var(--md-typeset-a-color)}.md-meta__link:focus,.md-meta__link:hover{color:var(--md-accent-fg-color)}.md-draft{background-color:#ff1744;border-radius:.125em;color:#fff;display:inline-block;font-weight:700;padding-left:.5714285714em;padding-right:.5714285714em}:root{--md-nav-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-nav-icon--next:url('data:image/svg+xml;charset=utf-8,');--md-toc-icon:url('data:image/svg+xml;charset=utf-8,')}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{color:var(--md-default-fg-color--light);display:block;font-weight:700;overflow:hidden;padding:0 .6rem;text-overflow:ellipsis}.md-nav__title .md-nav__button{display:none}.md-nav__title .md-nav__button img{height:100%;width:auto}.md-nav__title .md-nav__button.md-logo img,.md-nav__title .md-nav__button.md-logo svg{fill:currentcolor;display:block;height:2.4rem;max-width:100%;object-fit:contain;width:auto}.md-nav__list{list-style:none;margin:0;padding:0}.md-nav__link{align-items:flex-start;display:flex;gap:.4rem;margin-top:.625em;scroll-snap-align:start;transition:color 125ms}.md-nav__link--passed{color:var(--md-default-fg-color--light)}.md-nav__item .md-nav__link--active,.md-nav__item .md-nav__link--active code{color:var(--md-typeset-a-color)}.md-nav__link .md-ellipsis{position:relative}[dir=ltr] .md-nav__link .md-icon:last-child{margin-left:auto}[dir=rtl] .md-nav__link .md-icon:last-child{margin-right:auto}.md-nav__link svg{fill:currentcolor;flex-shrink:0;height:1.3em;position:relative}.md-nav__link[for]:focus,.md-nav__link[for]:hover,.md-nav__link[href]:focus,.md-nav__link[href]:hover{color:var(--md-accent-fg-color);cursor:pointer}.md-nav__link.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-nav--primary .md-nav__link[for=__toc]{display:none}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{background-color:currentcolor;display:block;height:100%;-webkit-mask-image:var(--md-toc-icon);mask-image:var(--md-toc-icon);width:100%}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:none}.md-nav__container>.md-nav__link{margin-top:0}.md-nav__container>.md-nav__link:first-child{flex-grow:1;min-width:0}.md-nav__icon{flex-shrink:0}.md-nav__source{display:none}@media screen and (max-width:76.234375em){.md-nav--primary,.md-nav--primary .md-nav{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;height:100%;left:0;position:absolute;right:0;top:0;z-index:1}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}.md-nav--primary .md-nav__title{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);cursor:pointer;height:5.6rem;line-height:2.4rem;padding:3rem .8rem .2rem;position:relative;white-space:nowrap}[dir=ltr] .md-nav--primary .md-nav__title .md-nav__icon{left:.4rem}[dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon{right:.4rem}.md-nav--primary .md-nav__title .md-nav__icon{display:block;height:1.2rem;margin:.2rem;position:absolute;top:.4rem;width:1.2rem}.md-nav--primary .md-nav__title .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--prev);mask-image:var(--md-nav-icon--prev);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}.md-nav--primary .md-nav__title~.md-nav__list{background-color:var(--md-default-bg-color);box-shadow:0 .05rem 0 var(--md-default-fg-color--lightest) inset;overflow-y:auto;scroll-snap-type:y mandatory;touch-action:pan-y}.md-nav--primary .md-nav__title~.md-nav__list>:first-child{border-top:0}.md-nav--primary .md-nav__title[for=__drawer]{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);font-weight:700}.md-nav--primary .md-nav__title .md-logo{display:block;left:.2rem;margin:.2rem;padding:.4rem;position:absolute;right:.2rem;top:.2rem}.md-nav--primary .md-nav__list{flex:1}.md-nav--primary .md-nav__item{border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-nav--primary .md-nav__item--active>.md-nav__link{color:var(--md-typeset-a-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:focus,.md-nav--primary .md-nav__item--active>.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link{margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link svg{margin-top:.1em}.md-nav--primary .md-nav__link>.md-nav__link{padding:0}[dir=ltr] .md-nav--primary .md-nav__link .md-nav__icon{margin-right:-.2rem}[dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon{margin-left:-.2rem}.md-nav--primary .md-nav__link .md-nav__icon{font-size:1.2rem;height:1.2rem;width:1.2rem}.md-nav--primary .md-nav__link .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-nav--primary .md-nav__icon:after{transform:scale(-1)}.md-nav--primary .md-nav--secondary .md-nav{background-color:initial;position:static}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem}.md-nav--secondary{background-color:initial}.md-nav__toggle~.md-nav{display:flex;opacity:0;transform:translateX(100%);transition:transform .25s cubic-bezier(.8,0,.6,1),opacity 125ms 50ms}[dir=rtl] .md-nav__toggle~.md-nav{transform:translateX(-100%)}.md-nav__toggle:checked~.md-nav{opacity:1;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 125ms 125ms}.md-nav__toggle:checked~.md-nav>.md-nav__list{-webkit-backface-visibility:hidden;backface-visibility:hidden}}@media screen and (max-width:59.984375em){.md-nav--primary .md-nav__link[for=__toc]{display:flex}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--primary .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:flex}.md-nav__source{background-color:var(--md-primary-fg-color--dark);color:var(--md-primary-bg-color);display:block;padding:0 .2rem}}@media screen and (min-width:60em) and (max-width:76.234375em){.md-nav--integrated .md-nav__link[for=__toc]{display:flex}.md-nav--integrated .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--integrated .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:flex}}@media screen and (min-width:60em){.md-nav{margin-bottom:-.4rem}.md-nav--secondary .md-nav__title{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);position:sticky;top:0;z-index:1}.md-nav--secondary .md-nav__title[for=__toc]{scroll-snap-align:start}.md-nav--secondary .md-nav__title .md-nav__icon{display:none}[dir=ltr] .md-nav--secondary .md-nav__list{padding-left:.6rem}[dir=rtl] .md-nav--secondary .md-nav__list{padding-right:.6rem}.md-nav--secondary .md-nav__list{padding-bottom:.4rem}[dir=ltr] .md-nav--secondary .md-nav__item>.md-nav__link{margin-right:.4rem}[dir=rtl] .md-nav--secondary .md-nav__item>.md-nav__link{margin-left:.4rem}}@media screen and (min-width:76.25em){.md-nav{margin-bottom:-.4rem;transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav--primary .md-nav__title{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);position:sticky;top:0;z-index:1}.md-nav--primary .md-nav__title[for=__drawer]{scroll-snap-align:start}.md-nav--primary .md-nav__title .md-nav__icon{display:none}[dir=ltr] .md-nav--primary .md-nav__list{padding-left:.6rem}[dir=rtl] .md-nav--primary .md-nav__list{padding-right:.6rem}.md-nav--primary .md-nav__list{padding-bottom:.4rem}[dir=ltr] .md-nav--primary .md-nav__item>.md-nav__link{margin-right:.4rem}[dir=rtl] .md-nav--primary .md-nav__item>.md-nav__link{margin-left:.4rem}.md-nav__toggle~.md-nav{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows .25s cubic-bezier(.86,0,.07,1),opacity .25s,visibility 0ms .25s;visibility:collapse}.md-nav__toggle~.md-nav>.md-nav__list{overflow:hidden}.md-nav__toggle.md-toggle--indeterminate~.md-nav,.md-nav__toggle:checked~.md-nav{grid-template-rows:1fr;opacity:1;transition:grid-template-rows .25s cubic-bezier(.86,0,.07,1),opacity .15s .1s,visibility 0ms;visibility:visible}.md-nav__toggle.md-toggle--indeterminate~.md-nav{transition:none}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--section{display:block;margin:1.25em 0}.md-nav__item--section:last-child{margin-bottom:0}.md-nav__item--section>.md-nav__link{font-weight:700}.md-nav__item--section>.md-nav__link[for]{color:var(--md-default-fg-color--light)}.md-nav__item--section>.md-nav__link:not(.md-nav__container){pointer-events:none}.md-nav__item--section>.md-nav__link .md-icon,.md-nav__item--section>.md-nav__link>[for]{display:none}[dir=ltr] .md-nav__item--section>.md-nav{margin-left:-.6rem}[dir=rtl] .md-nav__item--section>.md-nav{margin-right:-.6rem}.md-nav__item--section>.md-nav{display:block;opacity:1;visibility:visible}.md-nav__item--section>.md-nav>.md-nav__list>.md-nav__item{padding:0}.md-nav__icon{border-radius:100%;height:.9rem;transition:background-color .25s;width:.9rem}.md-nav__icon:hover{background-color:var(--md-accent-fg-color--transparent)}.md-nav__icon:after{background-color:currentcolor;border-radius:100%;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:transform .25s;vertical-align:-.1rem;width:100%}[dir=rtl] .md-nav__icon:after{transform:rotate(180deg)}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link .md-nav__icon:after,.md-nav__item--nested .md-toggle--indeterminate~.md-nav__link .md-nav__icon:after{transform:rotate(90deg)}.md-nav--lifted>.md-nav__list>.md-nav__item,.md-nav--lifted>.md-nav__title{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active{display:block}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);margin-top:0;position:sticky;top:0;z-index:1}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link:not(.md-nav__container){pointer-events:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active.md-nav__item--section{margin:0}[dir=ltr] .md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav:not(.md-nav--secondary){margin-left:-.6rem}[dir=rtl] .md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav:not(.md-nav--secondary){margin-right:-.6rem}.md-nav--lifted>.md-nav__list>.md-nav__item>[for]{color:var(--md-default-fg-color--light)}.md-nav--lifted .md-nav[data-md-level="1"]{grid-template-rows:1fr;opacity:1;visibility:visible}[dir=ltr] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-left:.05rem solid var(--md-primary-fg-color)}[dir=rtl] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-right:.05rem solid var(--md-primary-fg-color)}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{display:block;margin-bottom:1.25em;opacity:1;visibility:visible}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary>.md-nav__list{overflow:visible;padding-bottom:0}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary>.md-nav__title{display:none}}.md-pagination{font-size:.8rem;font-weight:700;gap:.4rem}.md-pagination,.md-pagination>*{align-items:center;display:flex;justify-content:center}.md-pagination>*{border-radius:.2rem;height:1.8rem;min-width:1.8rem;text-align:center}.md-pagination__current{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light)}.md-pagination__link{transition:color 125ms,background-color 125ms}.md-pagination__link:focus,.md-pagination__link:hover{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-pagination__link:focus svg,.md-pagination__link:hover svg{color:var(--md-accent-fg-color)}.md-pagination__link.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-pagination__link svg{fill:currentcolor;color:var(--md-default-fg-color--lighter);display:block;max-height:100%;width:1.2rem}.md-post__back{border-bottom:.05rem solid var(--md-default-fg-color--lightest);margin-bottom:1.2rem;padding-bottom:1.2rem}@media screen and (max-width:76.234375em){.md-post__back{display:none}}[dir=rtl] .md-post__back svg{transform:scaleX(-1)}.md-post__authors{display:flex;flex-direction:column;gap:.6rem;margin:0 .6rem 1.2rem}.md-post .md-post__meta a{transition:color 125ms}.md-post .md-post__meta a:focus,.md-post .md-post__meta a:hover{color:var(--md-accent-fg-color)}.md-post__title{color:var(--md-default-fg-color--light);font-weight:700}.md-post--excerpt{margin-bottom:3.2rem}.md-post--excerpt .md-post__header{align-items:center;display:flex;gap:.6rem;min-height:1.6rem}.md-post--excerpt .md-post__authors{align-items:center;display:inline-flex;flex-direction:row;gap:.2rem;margin:0;min-height:2.4rem}[dir=ltr] .md-post--excerpt .md-post__meta .md-meta__list{margin-right:.4rem}[dir=rtl] .md-post--excerpt .md-post__meta .md-meta__list{margin-left:.4rem}.md-post--excerpt .md-post__content>:first-child{--md-scroll-margin:6rem;margin-top:0}.md-post>.md-nav--secondary{margin:1em 0}.md-profile{align-items:center;display:flex;font-size:.7rem;gap:.6rem;line-height:1.4;width:100%}.md-profile__description{flex-grow:1}.md-content--post{display:flex}@media screen and (max-width:76.234375em){.md-content--post{flex-flow:column-reverse}}.md-content--post>.md-content__inner{min-width:0}@media screen and (min-width:76.25em){[dir=ltr] .md-content--post>.md-content__inner{margin-left:1.2rem}[dir=rtl] .md-content--post>.md-content__inner{margin-right:1.2rem}}@media screen and (max-width:76.234375em){.md-sidebar.md-sidebar--post{padding:0;position:static;width:100%}.md-sidebar.md-sidebar--post .md-sidebar__scrollwrap{overflow:visible}.md-sidebar.md-sidebar--post .md-sidebar__inner{padding:0}.md-sidebar.md-sidebar--post .md-post__meta{margin-left:.6rem;margin-right:.6rem}.md-sidebar.md-sidebar--post .md-nav__item{border:none;display:inline}.md-sidebar.md-sidebar--post .md-nav__list{display:inline-flex;flex-wrap:wrap;gap:.6rem;padding-bottom:.6rem;padding-top:.6rem}.md-sidebar.md-sidebar--post .md-nav__link{padding:0}.md-sidebar.md-sidebar--post .md-nav{height:auto;margin-bottom:0;position:static}}:root{--md-progress-value:0;--md-progress-delay:400ms}.md-progress{background:var(--md-primary-bg-color);height:.075rem;opacity:min(clamp(0,var(--md-progress-value),1),clamp(0,100 - var(--md-progress-value),1));position:fixed;top:0;transform:scaleX(calc(var(--md-progress-value)*1%));transform-origin:left;transition:transform .5s cubic-bezier(.19,1,.22,1),opacity .25s var(--md-progress-delay);width:100%;z-index:4}:root{--md-search-result-icon:url('data:image/svg+xml;charset=utf-8,')}.md-search{position:relative}@media screen and (min-width:60em){.md-search{padding:.2rem 0}}.no-js .md-search{display:none}.md-search__overlay{opacity:0;z-index:1}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__overlay{left:-2.2rem}[dir=rtl] .md-search__overlay{right:-2.2rem}.md-search__overlay{background-color:var(--md-default-bg-color);border-radius:1rem;height:2rem;overflow:hidden;pointer-events:none;position:absolute;top:-1rem;transform-origin:center;transition:transform .3s .1s,opacity .2s .2s;width:2rem}[data-md-toggle=search]:checked~.md-header .md-search__overlay{opacity:1;transition:transform .4s,opacity .1s}}@media screen and (min-width:60em){[dir=ltr] .md-search__overlay{left:0}[dir=rtl] .md-search__overlay{right:0}.md-search__overlay{background-color:#0000008a;cursor:pointer;height:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0}[data-md-toggle=search]:checked~.md-header .md-search__overlay{height:200vh;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@media screen and (max-width:29.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(45)}}@media screen and (min-width:30em) and (max-width:44.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(60)}}@media screen and (min-width:45em) and (max-width:59.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(75)}}.md-search__inner{-webkit-backface-visibility:hidden;backface-visibility:hidden}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__inner{left:0}[dir=rtl] .md-search__inner{right:0}.md-search__inner{height:0;opacity:0;overflow:hidden;position:fixed;top:0;transform:translateX(5%);transition:width 0ms .3s,height 0ms .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s;width:0;z-index:2}[dir=rtl] .md-search__inner{transform:translateX(-5%)}[data-md-toggle=search]:checked~.md-header .md-search__inner{height:100%;opacity:1;transform:translateX(0);transition:width 0ms 0ms,height 0ms 0ms,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s;width:100%}}@media screen and (min-width:60em){[dir=ltr] .md-search__inner{float:right}[dir=rtl] .md-search__inner{float:left}.md-search__inner{padding:.1rem 0;position:relative;transition:width .25s cubic-bezier(.1,.7,.1,1);width:11.7rem}}@media screen and (min-width:60em) and (max-width:76.234375em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}}@media screen and (min-width:76.25em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}}.md-search__form{background-color:var(--md-default-bg-color);box-shadow:0 0 .6rem #0000;height:2.4rem;position:relative;transition:color .25s,background-color .25s;z-index:2}@media screen and (min-width:60em){.md-search__form{background-color:#00000042;border-radius:.1rem;height:1.8rem}.md-search__form:hover{background-color:#ffffff1f}}[data-md-toggle=search]:checked~.md-header .md-search__form{background-color:var(--md-default-bg-color);border-radius:.1rem .1rem 0 0;box-shadow:0 0 .6rem #00000012;color:var(--md-default-fg-color)}[dir=ltr] .md-search__input{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__input{padding-left:2.2rem;padding-right:3.6rem}.md-search__input{background:#0000;font-size:.9rem;height:100%;position:relative;text-overflow:ellipsis;width:100%;z-index:2}.md-search__input::placeholder{transition:color .25s}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:var(--md-default-fg-color--light)}.md-search__input::-ms-clear{display:none}@media screen and (max-width:59.984375em){.md-search__input{font-size:.9rem;height:2.4rem;width:100%}}@media screen and (min-width:60em){[dir=ltr] .md-search__input{padding-left:2.2rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input{color:inherit;font-size:.8rem}.md-search__input::placeholder{color:var(--md-primary-bg-color--light)}.md-search__input+.md-search__icon{color:var(--md-primary-bg-color)}[data-md-toggle=search]:checked~.md-header .md-search__input{text-overflow:clip}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:#0000}}.md-search__icon{cursor:pointer;display:inline-block;height:1.2rem;transition:color .25s,opacity .25s;width:1.2rem}.md-search__icon:hover{opacity:.7}[dir=ltr] .md-search__icon[for=__search]{left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem}.md-search__icon[for=__search]{position:absolute;top:.3rem;z-index:2}[dir=rtl] .md-search__icon[for=__search] svg{transform:scaleX(-1)}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__icon[for=__search]{left:.8rem}[dir=rtl] .md-search__icon[for=__search]{right:.8rem}.md-search__icon[for=__search]{top:.6rem}.md-search__icon[for=__search] svg:first-child{display:none}}@media screen and (min-width:60em){.md-search__icon[for=__search]{pointer-events:none}.md-search__icon[for=__search] svg:last-child{display:none}}[dir=ltr] .md-search__options{right:.5rem}[dir=rtl] .md-search__options{left:.5rem}.md-search__options{pointer-events:none;position:absolute;top:.3rem;z-index:2}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__options{right:.8rem}[dir=rtl] .md-search__options{left:.8rem}.md-search__options{top:.6rem}}[dir=ltr] .md-search__options>.md-icon{margin-left:.2rem}[dir=rtl] .md-search__options>.md-icon{margin-right:.2rem}.md-search__options>.md-icon{color:var(--md-default-fg-color--light);opacity:0;transform:scale(.75);transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-search__options>.md-icon:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>.md-icon{opacity:1;pointer-events:auto;transform:scale(1)}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>.md-icon:hover{opacity:.7}[dir=ltr] .md-search__suggest{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__suggest{padding-left:2.2rem;padding-right:3.6rem}.md-search__suggest{align-items:center;color:var(--md-default-fg-color--lighter);display:flex;font-size:.9rem;height:100%;opacity:0;position:absolute;top:0;transition:opacity 50ms;white-space:nowrap;width:100%}@media screen and (min-width:60em){[dir=ltr] .md-search__suggest{padding-left:2.2rem}[dir=rtl] .md-search__suggest{padding-right:2.2rem}.md-search__suggest{font-size:.8rem}}[data-md-toggle=search]:checked~.md-header .md-search__suggest{opacity:1;transition:opacity .3s .1s}[dir=ltr] .md-search__output{border-bottom-left-radius:.1rem}[dir=ltr] .md-search__output,[dir=rtl] .md-search__output{border-bottom-right-radius:.1rem}[dir=rtl] .md-search__output{border-bottom-left-radius:.1rem}.md-search__output{overflow:hidden;position:absolute;width:100%;z-index:1}@media screen and (max-width:59.984375em){.md-search__output{bottom:0;top:2.4rem}}@media screen and (min-width:60em){.md-search__output{opacity:0;top:1.9rem;transition:opacity .4s}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:var(--md-shadow-z3);opacity:1}}.md-search__scrollwrap{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);height:100%;overflow-y:auto;touch-action:pan-y}@media (-webkit-max-device-pixel-ratio:1),(max-resolution:1dppx){.md-search__scrollwrap{transform:translateZ(0)}}@media screen and (min-width:60em) and (max-width:76.234375em){.md-search__scrollwrap{width:23.4rem}}@media screen and (min-width:76.25em){.md-search__scrollwrap{width:34.4rem}}@media screen and (min-width:60em){.md-search__scrollwrap{max-height:0;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-search__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}}.md-search-result{color:var(--md-default-fg-color);word-break:break-word}.md-search-result__meta{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.8rem;padding:0 .8rem;scroll-snap-align:start}@media screen and (min-width:60em){[dir=ltr] .md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem}}.md-search-result__list{list-style:none;margin:0;padding:0;-webkit-user-select:none;user-select:none}.md-search-result__item{box-shadow:0 -.05rem var(--md-default-fg-color--lightest)}.md-search-result__item:first-child{box-shadow:none}.md-search-result__link{display:block;outline:none;scroll-snap-align:start;transition:background-color .25s}.md-search-result__link:focus,.md-search-result__link:hover{background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:last-child p:last-child{margin-bottom:.6rem}.md-search-result__more>summary{cursor:pointer;display:block;outline:none;position:sticky;scroll-snap-align:start;top:0;z-index:1}.md-search-result__more>summary::marker{display:none}.md-search-result__more>summary::-webkit-details-marker{display:none}.md-search-result__more>summary>div{color:var(--md-typeset-a-color);font-size:.64rem;padding:.75em .8rem;transition:color .25s,background-color .25s}@media screen and (min-width:60em){[dir=ltr] .md-search-result__more>summary>div{padding-left:2.2rem}[dir=rtl] .md-search-result__more>summary>div{padding-right:2.2rem}}.md-search-result__more>summary:focus>div,.md-search-result__more>summary:hover>div{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-search-result__more[open]>summary{background-color:var(--md-default-bg-color)}.md-search-result__article{overflow:hidden;padding:0 .8rem;position:relative}@media screen and (min-width:60em){[dir=ltr] .md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem}}[dir=ltr] .md-search-result__icon{left:0}[dir=rtl] .md-search-result__icon{right:0}.md-search-result__icon{color:var(--md-default-fg-color--light);height:1.2rem;margin:.5rem;position:absolute;width:1.2rem}@media screen and (max-width:59.984375em){.md-search-result__icon{display:none}}.md-search-result__icon:after{background-color:currentcolor;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-search-result-icon);mask-image:var(--md-search-result-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-search-result__icon:after{transform:scaleX(-1)}.md-search-result .md-typeset{color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.6}.md-search-result .md-typeset h1{color:var(--md-default-fg-color);font-size:.8rem;font-weight:400;line-height:1.4;margin:.55rem 0}.md-search-result .md-typeset h1 mark{text-decoration:none}.md-search-result .md-typeset h2{color:var(--md-default-fg-color);font-size:.64rem;font-weight:700;line-height:1.6;margin:.5em 0}.md-search-result .md-typeset h2 mark{text-decoration:none}.md-search-result__terms{color:var(--md-default-fg-color);display:block;font-size:.64rem;font-style:italic;margin:.5em 0}.md-search-result mark{background-color:initial;color:var(--md-accent-fg-color);text-decoration:underline}.md-select{position:relative;z-index:1}.md-select__inner{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);left:50%;margin-top:.2rem;max-height:0;opacity:0;position:absolute;top:calc(100% - .2rem);transform:translate3d(-50%,.3rem,0);transition:transform .25s 375ms,opacity .25s .25s,max-height 0ms .5s}.md-select:focus-within .md-select__inner,.md-select:hover .md-select__inner{max-height:10rem;opacity:1;transform:translate3d(-50%,0,0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms}.md-select__inner:after{border-bottom:.2rem solid #0000;border-bottom-color:var(--md-default-bg-color);border-left:.2rem solid #0000;border-right:.2rem solid #0000;border-top:0;content:"";height:0;left:50%;margin-left:-.2rem;margin-top:-.2rem;position:absolute;top:0;width:0}.md-select__list{border-radius:.1rem;font-size:.8rem;list-style-type:none;margin:0;max-height:inherit;overflow:auto;padding:0}.md-select__item{line-height:1.8rem}[dir=ltr] .md-select__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-select__link{padding-left:1.2rem;padding-right:.6rem}.md-select__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:background-color .25s,color .25s;width:100%}.md-select__link:focus,.md-select__link:hover{color:var(--md-accent-fg-color)}.md-select__link:focus{background-color:var(--md-default-fg-color--lightest)}.md-sidebar{align-self:flex-start;flex-shrink:0;padding:1.2rem 0;position:sticky;top:2.4rem;width:12.1rem}@media print{.md-sidebar{display:none}}@media screen and (max-width:76.234375em){[dir=ltr] .md-sidebar--primary{left:-12.1rem}[dir=rtl] .md-sidebar--primary{right:-12.1rem}.md-sidebar--primary{background-color:var(--md-default-bg-color);display:block;height:100%;position:fixed;top:0;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s;width:12.1rem;z-index:5}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:var(--md-shadow-z3);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{transform:translateX(-12.1rem)}.md-sidebar--primary .md-sidebar__scrollwrap{bottom:0;left:0;margin:0;overflow:hidden;position:absolute;right:0;scroll-snap-type:none;top:0}}@media screen and (min-width:76.25em){.md-sidebar{height:0}.no-js .md-sidebar{height:auto}.md-header--lifted~.md-container .md-sidebar{top:4.8rem}}.md-sidebar--secondary{display:none;order:2}@media screen and (min-width:60em){.md-sidebar--secondary{height:0}.no-js .md-sidebar--secondary{height:auto}.md-sidebar--secondary:not([hidden]){display:block}.md-sidebar--secondary .md-sidebar__scrollwrap{touch-action:pan-y}}.md-sidebar__scrollwrap{scrollbar-gutter:stable;-webkit-backface-visibility:hidden;backface-visibility:hidden;margin:0 .2rem;overflow-y:auto;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin}.md-sidebar__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-sidebar__scrollwrap:focus-within,.md-sidebar__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-sidebar__scrollwrap:focus-within::-webkit-scrollbar-thumb,.md-sidebar__scrollwrap:hover::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-sidebar__scrollwrap:focus-within::-webkit-scrollbar-thumb:hover,.md-sidebar__scrollwrap:hover::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@supports selector(::-webkit-scrollbar){.md-sidebar__scrollwrap{scrollbar-gutter:auto}[dir=ltr] .md-sidebar__inner{padding-right:calc(100% - 11.5rem)}[dir=rtl] .md-sidebar__inner{padding-left:calc(100% - 11.5rem)}}@media screen and (max-width:76.234375em){.md-overlay{background-color:#0000008a;height:0;opacity:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0;z-index:5}[data-md-toggle=drawer]:checked~.md-overlay{height:100%;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@keyframes facts{0%{height:0}to{height:.65rem}}@keyframes fact{0%{opacity:0;transform:translateY(100%)}50%{opacity:0}to{opacity:1;transform:translateY(0)}}:root{--md-source-forks-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-repositories-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-stars-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-source{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:block;font-size:.65rem;line-height:1.2;outline-color:var(--md-accent-fg-color);transition:opacity .25s;white-space:nowrap}.md-source:hover{opacity:.7}.md-source__icon{display:inline-block;height:2.4rem;vertical-align:middle;width:2rem}[dir=ltr] .md-source__icon svg{margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem}.md-source__icon svg{margin-top:.6rem}[dir=ltr] .md-source__icon+.md-source__repository{padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{padding-right:2rem}[dir=ltr] .md-source__icon+.md-source__repository{margin-left:-2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem}[dir=ltr] .md-source__repository{margin-left:.6rem}[dir=rtl] .md-source__repository{margin-right:.6rem}.md-source__repository{display:inline-block;max-width:calc(100% - 1.2rem);overflow:hidden;text-overflow:ellipsis;vertical-align:middle}.md-source__facts{display:flex;font-size:.55rem;gap:.4rem;list-style-type:none;margin:.1rem 0 0;opacity:.75;overflow:hidden;padding:0;width:100%}.md-source__repository--active .md-source__facts{animation:facts .25s ease-in}.md-source__fact{overflow:hidden;text-overflow:ellipsis}.md-source__repository--active .md-source__fact{animation:fact .4s ease-out}[dir=ltr] .md-source__fact:before{margin-right:.1rem}[dir=rtl] .md-source__fact:before{margin-left:.1rem}.md-source__fact:before{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:text-top;width:.6rem}.md-source__fact:nth-child(1n+2){flex-shrink:0}.md-source__fact--version:before{-webkit-mask-image:var(--md-source-version-icon);mask-image:var(--md-source-version-icon)}.md-source__fact--stars:before{-webkit-mask-image:var(--md-source-stars-icon);mask-image:var(--md-source-stars-icon)}.md-source__fact--forks:before{-webkit-mask-image:var(--md-source-forks-icon);mask-image:var(--md-source-forks-icon)}.md-source__fact--repositories:before{-webkit-mask-image:var(--md-source-repositories-icon);mask-image:var(--md-source-repositories-icon)}.md-source-file{margin:1em 0}[dir=ltr] .md-source-file__fact{margin-right:.6rem}[dir=rtl] .md-source-file__fact{margin-left:.6rem}.md-source-file__fact{align-items:center;color:var(--md-default-fg-color--light);display:inline-flex;font-size:.68rem;gap:.3rem}.md-source-file__fact .md-icon{flex-shrink:0;margin-bottom:.05rem}[dir=ltr] .md-source-file__fact .md-author{float:left}[dir=rtl] .md-source-file__fact .md-author{float:right}.md-source-file__fact .md-author{margin-right:.2rem}.md-source-file__fact svg{width:.9rem}:root{--md-status:url('data:image/svg+xml;charset=utf-8,');--md-status--new:url('data:image/svg+xml;charset=utf-8,');--md-status--deprecated:url('data:image/svg+xml;charset=utf-8,');--md-status--encrypted:url('data:image/svg+xml;charset=utf-8,')}.md-status:after{background-color:var(--md-default-fg-color--light);content:"";display:inline-block;height:1.125em;-webkit-mask-image:var(--md-status);mask-image:var(--md-status);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:text-bottom;width:1.125em}.md-status:hover:after{background-color:currentcolor}.md-status--new:after{-webkit-mask-image:var(--md-status--new);mask-image:var(--md-status--new)}.md-status--deprecated:after{-webkit-mask-image:var(--md-status--deprecated);mask-image:var(--md-status--deprecated)}.md-status--encrypted:after{-webkit-mask-image:var(--md-status--encrypted);mask-image:var(--md-status--encrypted)}.md-tabs{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);display:block;line-height:1.3;overflow:auto;width:100%;z-index:3}@media print{.md-tabs{display:none}}@media screen and (max-width:76.234375em){.md-tabs{display:none}}.md-tabs[hidden]{pointer-events:none}[dir=ltr] .md-tabs__list{margin-left:.2rem}[dir=rtl] .md-tabs__list{margin-right:.2rem}.md-tabs__list{contain:content;display:flex;list-style:none;margin:0;overflow:auto;padding:0;scrollbar-width:none;white-space:nowrap}.md-tabs__list::-webkit-scrollbar{display:none}.md-tabs__item{height:2.4rem;padding-left:.6rem;padding-right:.6rem}.md-tabs__item--active .md-tabs__link{color:inherit;opacity:1}.md-tabs__link{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:flex;font-size:.7rem;margin-top:.8rem;opacity:.7;outline-color:var(--md-accent-fg-color);outline-offset:.2rem;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s}.md-tabs__link:focus,.md-tabs__link:hover{color:inherit;opacity:1}[dir=ltr] .md-tabs__link svg{margin-right:.4rem}[dir=rtl] .md-tabs__link svg{margin-left:.4rem}.md-tabs__link svg{fill:currentcolor;height:1.3em}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:20ms}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:40ms}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:60ms}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:80ms}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[hidden] .md-tabs__link{opacity:0;transform:translateY(50%);transition:transform 0ms .1s,opacity .1s}:root{--md-tag-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .md-tags:not([hidden]){display:inline-flex;flex-wrap:wrap;gap:.5em;margin-bottom:.75em;margin-top:-.125em}.md-typeset .md-tag{align-items:center;background:var(--md-default-fg-color--lightest);border-radius:2.4rem;display:inline-flex;font-size:.64rem;font-size:min(.8em,.64rem);font-weight:700;gap:.5em;letter-spacing:normal;line-height:1.6;padding:.3125em .78125em}.md-typeset .md-tag[href]{-webkit-tap-highlight-color:transparent;color:inherit;outline:none;transition:color 125ms,background-color 125ms}.md-typeset .md-tag[href]:focus,.md-typeset .md-tag[href]:hover{background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[id]>.md-typeset .md-tag{vertical-align:text-top}.md-typeset .md-tag-icon:before{background-color:var(--md-default-fg-color--lighter);content:"";display:inline-block;height:1.2em;-webkit-mask-image:var(--md-tag-icon);mask-image:var(--md-tag-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color 125ms;vertical-align:text-bottom;width:1.2em}.md-typeset .md-tag-icon[href]:focus:before,.md-typeset .md-tag-icon[href]:hover:before{background-color:var(--md-accent-bg-color)}@keyframes pulse{0%{transform:scale(.95)}75%{transform:scale(1)}to{transform:scale(.95)}}:root{--md-annotation-bg-icon:url('data:image/svg+xml;charset=utf-8,');--md-annotation-icon:url('data:image/svg+xml;charset=utf-8,')}.md-tooltip{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);font-family:var(--md-text-font-family);left:clamp(var(--md-tooltip-0,0rem) + .8rem,var(--md-tooltip-x),100vw + var(--md-tooltip-0,0rem) + .8rem - var(--md-tooltip-width) - 2 * .8rem);max-width:calc(100vw - 1.6rem);opacity:0;position:absolute;top:var(--md-tooltip-y);transform:translateY(-.4rem);transition:transform 0ms .25s,opacity .25s,z-index .25s;width:var(--md-tooltip-width);z-index:0}.md-tooltip--active{opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,z-index 0ms;z-index:2}.md-tooltip--inline{font-weight:700;-webkit-user-select:none;user-select:none;width:auto}.md-tooltip--inline:not(.md-tooltip--active){transform:translateY(.2rem) scale(.9)}.md-tooltip--inline .md-tooltip__inner{font-size:.5rem;padding:.2rem .4rem}[hidden]+.md-tooltip--inline{display:none}.focus-visible>.md-tooltip,.md-tooltip:target{outline:var(--md-accent-fg-color) auto}.md-tooltip__inner{font-size:.64rem;padding:.8rem}.md-tooltip__inner.md-typeset>:first-child{margin-top:0}.md-tooltip__inner.md-typeset>:last-child{margin-bottom:0}.md-annotation{font-style:normal;font-weight:400;outline:none;text-align:initial;vertical-align:text-bottom;white-space:normal}[dir=rtl] .md-annotation{direction:rtl}code .md-annotation{font-family:var(--md-code-font-family);font-size:inherit}.md-annotation:not([hidden]){display:inline-block;line-height:1.25}.md-annotation__index{border-radius:.01px;cursor:pointer;display:inline-block;margin-left:.4ch;margin-right:.4ch;outline:none;overflow:hidden;position:relative;-webkit-user-select:none;user-select:none;vertical-align:text-top;z-index:0}.md-annotation .md-annotation__index{transition:z-index .25s}@media screen{.md-annotation__index{width:2.2ch}[data-md-visible]>.md-annotation__index{animation:pulse 2s infinite}.md-annotation__index:before{background:var(--md-default-bg-color);-webkit-mask-image:var(--md-annotation-bg-icon);mask-image:var(--md-annotation-bg-icon)}.md-annotation__index:after,.md-annotation__index:before{content:"";height:2.2ch;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:-.1ch;width:2.2ch;z-index:-1}.md-annotation__index:after{background-color:var(--md-default-fg-color--lighter);-webkit-mask-image:var(--md-annotation-icon);mask-image:var(--md-annotation-icon);transform:scale(1.0001);transition:background-color .25s,transform .25s}.md-tooltip--active+.md-annotation__index:after{transform:rotate(45deg)}.md-tooltip--active+.md-annotation__index:after,:hover>.md-annotation__index:after{background-color:var(--md-accent-fg-color)}}.md-tooltip--active+.md-annotation__index{animation-play-state:paused;transition-duration:0ms;z-index:2}.md-annotation__index [data-md-annotation-id]{display:inline-block}@media print{.md-annotation__index [data-md-annotation-id]{background:var(--md-default-fg-color--lighter);border-radius:2ch;color:var(--md-default-bg-color);font-weight:700;padding:0 .6ch;white-space:nowrap}.md-annotation__index [data-md-annotation-id]:after{content:attr(data-md-annotation-id)}}.md-typeset .md-annotation-list{counter-reset:xxx;list-style:none}.md-typeset .md-annotation-list li{position:relative}[dir=ltr] .md-typeset .md-annotation-list li:before{left:-2.125em}[dir=rtl] .md-typeset .md-annotation-list li:before{right:-2.125em}.md-typeset .md-annotation-list li:before{background:var(--md-default-fg-color--lighter);border-radius:2ch;color:var(--md-default-bg-color);content:counter(xxx);counter-increment:xxx;font-size:.8875em;font-weight:700;height:2ch;line-height:1.25;min-width:2ch;padding:0 .6ch;position:absolute;text-align:center;top:.25em}:root{--md-tooltip-width:20rem;--md-tooltip-tail:0.3rem}.md-tooltip2{-webkit-backface-visibility:hidden;backface-visibility:hidden;color:var(--md-default-fg-color);font-family:var(--md-text-font-family);opacity:0;pointer-events:none;position:absolute;top:calc(var(--md-tooltip-host-y) + var(--md-tooltip-y));transform:translateY(-.4rem);transform-origin:calc(var(--md-tooltip-host-x) + var(--md-tooltip-x)) 0;transition:transform 0ms .25s,opacity .25s,z-index .25s;width:100%;z-index:0}.md-tooltip2:before{border-left:var(--md-tooltip-tail) solid #0000;border-right:var(--md-tooltip-tail) solid #0000;content:"";display:block;left:clamp(1.5 * .8rem,var(--md-tooltip-host-x) + var(--md-tooltip-x) - var(--md-tooltip-tail),100vw - 2 * var(--md-tooltip-tail) - 1.5 * .8rem);position:absolute;z-index:1}.md-tooltip2--top:before{border-top:var(--md-tooltip-tail) solid var(--md-default-bg-color);bottom:calc(var(--md-tooltip-tail)*-1 + .025rem);filter:drop-shadow(0 1px 0 hsla(0,0%,0%,.05))}.md-tooltip2--bottom:before{border-bottom:var(--md-tooltip-tail) solid var(--md-default-bg-color);filter:drop-shadow(0 -1px 0 hsla(0,0%,0%,.05));top:calc(var(--md-tooltip-tail)*-1 + .025rem)}.md-tooltip2--active{opacity:1;transform:translateY(0);transition:transform .4s cubic-bezier(0,1,.5,1),opacity .25s,z-index 0ms;z-index:2}.md-tooltip2__inner{scrollbar-gutter:stable;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);left:clamp(.8rem,var(--md-tooltip-host-x) - .8rem,100vw - var(--md-tooltip-width) - .8rem);max-height:40vh;max-width:calc(100vw - 1.6rem);position:relative;scrollbar-width:thin}.md-tooltip2__inner::-webkit-scrollbar{height:.2rem;width:.2rem}.md-tooltip2__inner::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-tooltip2__inner::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}[role=tooltip]>.md-tooltip2__inner{font-size:.5rem;font-weight:700;left:clamp(.8rem,var(--md-tooltip-host-x) + var(--md-tooltip-x) - var(--md-tooltip-width)/2,100vw - var(--md-tooltip-width) - .8rem);max-width:min(100vw - 2 * .8rem,400px);padding:.2rem .4rem;-webkit-user-select:none;user-select:none;width:-moz-fit-content;width:fit-content}.md-tooltip2__inner.md-typeset>:first-child{margin-top:0}.md-tooltip2__inner.md-typeset>:last-child{margin-bottom:0}[dir=ltr] .md-top{margin-left:50%}[dir=rtl] .md-top{margin-right:50%}.md-top{background-color:var(--md-default-bg-color);border-radius:1.6rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color--light);cursor:pointer;display:block;font-size:.7rem;outline:none;padding:.4rem .8rem;position:fixed;top:3.2rem;transform:translate(-50%);transition:color 125ms,background-color 125ms,transform 125ms cubic-bezier(.4,0,.2,1),opacity 125ms;z-index:2}@media print{.md-top{display:none}}[dir=rtl] .md-top{transform:translate(50%)}.md-top[hidden]{opacity:0;pointer-events:none;transform:translate(-50%,.2rem);transition-duration:0ms}[dir=rtl] .md-top[hidden]{transform:translate(50%,.2rem)}.md-top:focus,.md-top:hover{background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-top svg{display:inline-block;vertical-align:-.5em}@keyframes hoverfix{0%{pointer-events:none}}:root{--md-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-version{flex-shrink:0;font-size:.8rem;height:2.4rem}[dir=ltr] .md-version__current{margin-left:1.4rem;margin-right:.4rem}[dir=rtl] .md-version__current{margin-left:.4rem;margin-right:1.4rem}.md-version__current{color:inherit;cursor:pointer;outline:none;position:relative;top:.05rem}[dir=ltr] .md-version__current:after{margin-left:.4rem}[dir=rtl] .md-version__current:after{margin-right:.4rem}.md-version__current:after{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-image:var(--md-version-icon);mask-image:var(--md-version-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.4rem}.md-version__alias{margin-left:.3rem;opacity:.7}.md-version__list{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);list-style-type:none;margin:.2rem .8rem;max-height:0;opacity:0;overflow:auto;padding:0;position:absolute;scroll-snap-type:y mandatory;top:.15rem;transition:max-height 0ms .5s,opacity .25s .25s;z-index:3}.md-version:focus-within .md-version__list,.md-version:hover .md-version__list{max-height:10rem;opacity:1;transition:max-height 0ms,opacity .25s}@media (hover:none),(pointer:coarse){.md-version:hover .md-version__list{animation:hoverfix .25s forwards}.md-version:focus-within .md-version__list{animation:none}}.md-version__item{line-height:1.8rem}[dir=ltr] .md-version__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-version__link{padding-left:1.2rem;padding-right:.6rem}.md-version__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:color .25s,background-color .25s;white-space:nowrap;width:100%}.md-version__link:focus,.md-version__link:hover{color:var(--md-accent-fg-color)}.md-version__link:focus{background-color:var(--md-default-fg-color--lightest)}:root{--md-admonition-icon--note:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--abstract:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--info:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--tip:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--success:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--question:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--warning:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--failure:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--danger:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--bug:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--example:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--quote:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .admonition,.md-typeset details{background-color:var(--md-admonition-bg-color);border:.075rem solid #448aff;border-radius:.2rem;box-shadow:var(--md-shadow-z1);color:var(--md-admonition-fg-color);display:flow-root;font-size:.64rem;margin:1.5625em 0;padding:0 .6rem;page-break-inside:avoid;transition:box-shadow 125ms}@media print{.md-typeset .admonition,.md-typeset details{box-shadow:none}}.md-typeset .admonition:focus-within,.md-typeset details:focus-within{box-shadow:0 0 0 .2rem #448aff1a}.md-typeset .admonition>*,.md-typeset details>*{box-sizing:border-box}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin-bottom:1em;margin-top:1em}.md-typeset .admonition .md-typeset__scrollwrap,.md-typeset details .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset .admonition .md-typeset__table,.md-typeset details .md-typeset__table{padding:0 .6rem}.md-typeset .admonition>.tabbed-set:only-child,.md-typeset details>.tabbed-set:only-child{margin-top:0}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{padding-left:2rem;padding-right:.6rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{padding-left:.6rem;padding-right:2rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{border-left-width:.2rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-right-width:.2rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{border-top-left-radius:.1rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary,[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-top-right-radius:.1rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-top-left-radius:.1rem}.md-typeset .admonition-title,.md-typeset summary{background-color:#448aff1a;border:none;font-weight:700;margin:0 -.6rem;padding-bottom:.4rem;padding-top:.4rem;position:relative}html .md-typeset .admonition-title:last-child,html .md-typeset summary:last-child{margin-bottom:0}[dir=ltr] .md-typeset .admonition-title:before,[dir=ltr] .md-typeset summary:before{left:.6rem}[dir=rtl] .md-typeset .admonition-title:before,[dir=rtl] .md-typeset summary:before{right:.6rem}.md-typeset .admonition-title:before,.md-typeset summary:before{background-color:#448aff;content:"";height:1rem;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;width:1rem}.md-typeset .admonition-title code,.md-typeset summary code{box-shadow:0 0 0 .05rem var(--md-default-fg-color--lightest)}.md-typeset .admonition.note,.md-typeset details.note{border-color:#448aff}.md-typeset .admonition.note:focus-within,.md-typeset details.note:focus-within{box-shadow:0 0 0 .2rem #448aff1a}.md-typeset .note>.admonition-title,.md-typeset .note>summary{background-color:#448aff1a}.md-typeset .note>.admonition-title:before,.md-typeset .note>summary:before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note)}.md-typeset .note>.admonition-title:after,.md-typeset .note>summary:after{color:#448aff}.md-typeset .admonition.abstract,.md-typeset details.abstract{border-color:#00b0ff}.md-typeset .admonition.abstract:focus-within,.md-typeset details.abstract:focus-within{box-shadow:0 0 0 .2rem #00b0ff1a}.md-typeset .abstract>.admonition-title,.md-typeset .abstract>summary{background-color:#00b0ff1a}.md-typeset .abstract>.admonition-title:before,.md-typeset .abstract>summary:before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract)}.md-typeset .abstract>.admonition-title:after,.md-typeset .abstract>summary:after{color:#00b0ff}.md-typeset .admonition.info,.md-typeset details.info{border-color:#00b8d4}.md-typeset .admonition.info:focus-within,.md-typeset details.info:focus-within{box-shadow:0 0 0 .2rem #00b8d41a}.md-typeset .info>.admonition-title,.md-typeset .info>summary{background-color:#00b8d41a}.md-typeset .info>.admonition-title:before,.md-typeset .info>summary:before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info)}.md-typeset .info>.admonition-title:after,.md-typeset .info>summary:after{color:#00b8d4}.md-typeset .admonition.tip,.md-typeset details.tip{border-color:#00bfa5}.md-typeset .admonition.tip:focus-within,.md-typeset details.tip:focus-within{box-shadow:0 0 0 .2rem #00bfa51a}.md-typeset .tip>.admonition-title,.md-typeset .tip>summary{background-color:#00bfa51a}.md-typeset .tip>.admonition-title:before,.md-typeset .tip>summary:before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip)}.md-typeset .tip>.admonition-title:after,.md-typeset .tip>summary:after{color:#00bfa5}.md-typeset .admonition.success,.md-typeset details.success{border-color:#00c853}.md-typeset .admonition.success:focus-within,.md-typeset details.success:focus-within{box-shadow:0 0 0 .2rem #00c8531a}.md-typeset .success>.admonition-title,.md-typeset .success>summary{background-color:#00c8531a}.md-typeset .success>.admonition-title:before,.md-typeset .success>summary:before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success)}.md-typeset .success>.admonition-title:after,.md-typeset .success>summary:after{color:#00c853}.md-typeset .admonition.question,.md-typeset details.question{border-color:#64dd17}.md-typeset .admonition.question:focus-within,.md-typeset details.question:focus-within{box-shadow:0 0 0 .2rem #64dd171a}.md-typeset .question>.admonition-title,.md-typeset .question>summary{background-color:#64dd171a}.md-typeset .question>.admonition-title:before,.md-typeset .question>summary:before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question)}.md-typeset .question>.admonition-title:after,.md-typeset .question>summary:after{color:#64dd17}.md-typeset .admonition.warning,.md-typeset details.warning{border-color:#ff9100}.md-typeset .admonition.warning:focus-within,.md-typeset details.warning:focus-within{box-shadow:0 0 0 .2rem #ff91001a}.md-typeset .warning>.admonition-title,.md-typeset .warning>summary{background-color:#ff91001a}.md-typeset .warning>.admonition-title:before,.md-typeset .warning>summary:before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning)}.md-typeset .warning>.admonition-title:after,.md-typeset .warning>summary:after{color:#ff9100}.md-typeset .admonition.failure,.md-typeset details.failure{border-color:#ff5252}.md-typeset .admonition.failure:focus-within,.md-typeset details.failure:focus-within{box-shadow:0 0 0 .2rem #ff52521a}.md-typeset .failure>.admonition-title,.md-typeset .failure>summary{background-color:#ff52521a}.md-typeset .failure>.admonition-title:before,.md-typeset .failure>summary:before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure)}.md-typeset .failure>.admonition-title:after,.md-typeset .failure>summary:after{color:#ff5252}.md-typeset .admonition.danger,.md-typeset details.danger{border-color:#ff1744}.md-typeset .admonition.danger:focus-within,.md-typeset details.danger:focus-within{box-shadow:0 0 0 .2rem #ff17441a}.md-typeset .danger>.admonition-title,.md-typeset .danger>summary{background-color:#ff17441a}.md-typeset .danger>.admonition-title:before,.md-typeset .danger>summary:before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger)}.md-typeset .danger>.admonition-title:after,.md-typeset .danger>summary:after{color:#ff1744}.md-typeset .admonition.bug,.md-typeset details.bug{border-color:#f50057}.md-typeset .admonition.bug:focus-within,.md-typeset details.bug:focus-within{box-shadow:0 0 0 .2rem #f500571a}.md-typeset .bug>.admonition-title,.md-typeset .bug>summary{background-color:#f500571a}.md-typeset .bug>.admonition-title:before,.md-typeset .bug>summary:before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug)}.md-typeset .bug>.admonition-title:after,.md-typeset .bug>summary:after{color:#f50057}.md-typeset .admonition.example,.md-typeset details.example{border-color:#7c4dff}.md-typeset .admonition.example:focus-within,.md-typeset details.example:focus-within{box-shadow:0 0 0 .2rem #7c4dff1a}.md-typeset .example>.admonition-title,.md-typeset .example>summary{background-color:#7c4dff1a}.md-typeset .example>.admonition-title:before,.md-typeset .example>summary:before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example)}.md-typeset .example>.admonition-title:after,.md-typeset .example>summary:after{color:#7c4dff}.md-typeset .admonition.quote,.md-typeset details.quote{border-color:#9e9e9e}.md-typeset .admonition.quote:focus-within,.md-typeset details.quote:focus-within{box-shadow:0 0 0 .2rem #9e9e9e1a}.md-typeset .quote>.admonition-title,.md-typeset .quote>summary{background-color:#9e9e9e1a}.md-typeset .quote>.admonition-title:before,.md-typeset .quote>summary:before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote)}.md-typeset .quote>.admonition-title:after,.md-typeset .quote>summary:after{color:#9e9e9e}:root{--md-footnotes-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .footnote{color:var(--md-default-fg-color--light);font-size:.64rem}[dir=ltr] .md-typeset .footnote>ol{margin-left:0}[dir=rtl] .md-typeset .footnote>ol{margin-right:0}.md-typeset .footnote>ol>li{transition:color 125ms}.md-typeset .footnote>ol>li:target{color:var(--md-default-fg-color)}.md-typeset .footnote>ol>li:focus-within .footnote-backref{opacity:1;transform:translateX(0);transition:none}.md-typeset .footnote>ol>li:hover .footnote-backref,.md-typeset .footnote>ol>li:target .footnote-backref{opacity:1;transform:translateX(0)}.md-typeset .footnote>ol>li>:first-child{margin-top:0}.md-typeset .footnote-ref{font-size:.75em;font-weight:700}html .md-typeset .footnote-ref{outline-offset:.1rem}.md-typeset [id^="fnref:"]:target>.footnote-ref{outline:auto}.md-typeset .footnote-backref{color:var(--md-typeset-a-color);display:inline-block;font-size:0;opacity:0;transform:translateX(.25rem);transition:color .25s,transform .25s .25s,opacity 125ms .25s;vertical-align:text-bottom}@media print{.md-typeset .footnote-backref{color:var(--md-typeset-a-color);opacity:1;transform:translateX(0)}}[dir=rtl] .md-typeset .footnote-backref{transform:translateX(-.25rem)}.md-typeset .footnote-backref:hover{color:var(--md-accent-fg-color)}.md-typeset .footnote-backref:before{background-color:currentcolor;content:"";display:inline-block;height:.8rem;-webkit-mask-image:var(--md-footnotes-icon);mask-image:var(--md-footnotes-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.8rem}[dir=rtl] .md-typeset .footnote-backref:before svg{transform:scaleX(-1)}[dir=ltr] .md-typeset .headerlink{margin-left:.5rem}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem}.md-typeset .headerlink{color:var(--md-default-fg-color--lighter);display:inline-block;opacity:0;transition:color .25s,opacity 125ms}@media print{.md-typeset .headerlink{display:none}}.md-typeset .headerlink:focus,.md-typeset :hover>.headerlink,.md-typeset :target>.headerlink{opacity:1;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset .headerlink:hover,.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset :target{--md-scroll-margin:3.6rem;--md-scroll-offset:0rem;scroll-margin-top:calc(var(--md-scroll-margin) - var(--md-scroll-offset))}@media screen and (min-width:76.25em){.md-header--lifted~.md-container .md-typeset :target{--md-scroll-margin:6rem}}.md-typeset h1:target,.md-typeset h2:target,.md-typeset h3:target{--md-scroll-offset:0.2rem}.md-typeset h4:target{--md-scroll-offset:0.15rem}.md-typeset div.arithmatex{overflow:auto}@media screen and (max-width:44.984375em){.md-typeset div.arithmatex{margin:0 -.8rem}.md-typeset div.arithmatex>*{width:min-content}}.md-typeset div.arithmatex>*{margin-left:auto!important;margin-right:auto!important;padding:0 .8rem;touch-action:auto}.md-typeset div.arithmatex>* mjx-container{margin:0!important}.md-typeset div.arithmatex mjx-assistive-mml{height:0}.md-typeset del.critic{background-color:var(--md-typeset-del-color)}.md-typeset del.critic,.md-typeset ins.critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset ins.critic{background-color:var(--md-typeset-ins-color)}.md-typeset .critic.comment{-webkit-box-decoration-break:clone;box-decoration-break:clone;color:var(--md-code-hl-comment-color)}.md-typeset .critic.comment:before{content:"/* "}.md-typeset .critic.comment:after{content:" */"}.md-typeset .critic.block{box-shadow:none;display:block;margin:1em 0;overflow:auto;padding-left:.8rem;padding-right:.8rem}.md-typeset .critic.block>:first-child{margin-top:.5em}.md-typeset .critic.block>:last-child{margin-bottom:.5em}:root{--md-details-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset details{display:flow-root;overflow:visible;padding-top:0}.md-typeset details[open]>summary:after{transform:rotate(90deg)}.md-typeset details:not([open]){box-shadow:none;padding-bottom:0}.md-typeset details:not([open])>summary{border-radius:.1rem}[dir=ltr] .md-typeset summary{padding-right:1.8rem}[dir=rtl] .md-typeset summary{padding-left:1.8rem}[dir=ltr] .md-typeset summary{border-top-left-radius:.1rem}[dir=ltr] .md-typeset summary,[dir=rtl] .md-typeset summary{border-top-right-radius:.1rem}[dir=rtl] .md-typeset summary{border-top-left-radius:.1rem}.md-typeset summary{cursor:pointer;display:block;min-height:1rem;overflow:hidden}.md-typeset summary.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset summary:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[dir=ltr] .md-typeset summary:after{right:.4rem}[dir=rtl] .md-typeset summary:after{left:.4rem}.md-typeset summary:after{background-color:currentcolor;content:"";height:1rem;-webkit-mask-image:var(--md-details-icon);mask-image:var(--md-details-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;transform:rotate(0deg);transition:transform .25s;width:1rem}[dir=rtl] .md-typeset summary:after{transform:rotate(180deg)}.md-typeset summary::marker{display:none}.md-typeset summary::-webkit-details-marker{display:none}.md-typeset .emojione,.md-typeset .gemoji,.md-typeset .twemoji{--md-icon-size:1.125em;display:inline-flex;height:var(--md-icon-size);vertical-align:text-top}.md-typeset .emojione svg,.md-typeset .gemoji svg,.md-typeset .twemoji svg{fill:currentcolor;max-height:100%;width:var(--md-icon-size)}.md-typeset .lg,.md-typeset .xl,.md-typeset .xxl,.md-typeset .xxxl{vertical-align:text-bottom}.md-typeset .middle{vertical-align:middle}.md-typeset .lg{--md-icon-size:1.5em}.md-typeset .xl{--md-icon-size:2.25em}.md-typeset .xxl{--md-icon-size:3em}.md-typeset .xxxl{--md-icon-size:4em}.highlight .o,.highlight .ow{color:var(--md-code-hl-operator-color)}.highlight .p{color:var(--md-code-hl-punctuation-color)}.highlight .cpf,.highlight .l,.highlight .s,.highlight .s1,.highlight .s2,.highlight .sb,.highlight .sc,.highlight .si,.highlight .ss{color:var(--md-code-hl-string-color)}.highlight .cp,.highlight .se,.highlight .sh,.highlight .sr,.highlight .sx{color:var(--md-code-hl-special-color)}.highlight .il,.highlight .m,.highlight .mb,.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:var(--md-code-hl-number-color)}.highlight .k,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr,.highlight .kt{color:var(--md-code-hl-keyword-color)}.highlight .kc,.highlight .n{color:var(--md-code-hl-name-color)}.highlight .bp,.highlight .nb,.highlight .no{color:var(--md-code-hl-constant-color)}.highlight .nc,.highlight .ne,.highlight .nf,.highlight .nn{color:var(--md-code-hl-function-color)}.highlight .nd,.highlight .ni,.highlight .nl,.highlight .nt{color:var(--md-code-hl-keyword-color)}.highlight .c,.highlight .c1,.highlight .ch,.highlight .cm,.highlight .cs,.highlight .sd{color:var(--md-code-hl-comment-color)}.highlight .na,.highlight .nv,.highlight .vc,.highlight .vg,.highlight .vi{color:var(--md-code-hl-variable-color)}.highlight .ge,.highlight .gh,.highlight .go,.highlight .gp,.highlight .gr,.highlight .gs,.highlight .gt,.highlight .gu{color:var(--md-code-hl-generic-color)}.highlight .gd,.highlight .gi{border-radius:.1rem;margin:0 -.125em;padding:0 .125em}.highlight .gd{background-color:var(--md-typeset-del-color)}.highlight .gi{background-color:var(--md-typeset-ins-color)}.highlight .hll{background-color:var(--md-code-hl-color--light);box-shadow:2px 0 0 0 var(--md-code-hl-color) inset;display:block;margin:0 -1.1764705882em;padding:0 1.1764705882em}.highlight span.filename{background-color:var(--md-code-bg-color);border-bottom:.05rem solid var(--md-default-fg-color--lightest);border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:flow-root;font-size:.85em;font-weight:700;margin-top:1em;padding:.6617647059em 1.1764705882em;position:relative}.highlight span.filename+pre{margin-top:0}.highlight span.filename+pre>code{border-top-left-radius:0;border-top-right-radius:0}.highlight [data-linenos]:before{background-color:var(--md-code-bg-color);box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;color:var(--md-default-fg-color--light);content:attr(data-linenos);float:left;left:-1.1764705882em;margin-left:-1.1764705882em;margin-right:1.1764705882em;padding-left:1.1764705882em;position:sticky;-webkit-user-select:none;user-select:none;z-index:3}.highlight code a[id]{position:absolute;visibility:hidden}.highlight code[data-md-copying]{display:initial}.highlight code[data-md-copying] .hll{display:contents}.highlight code[data-md-copying] .md-annotation{display:none}.highlighttable{display:flow-root}.highlighttable tbody,.highlighttable td{display:block;padding:0}.highlighttable tr{display:flex}.highlighttable pre{margin:0}.highlighttable th.filename{flex-grow:1;padding:0;text-align:left}.highlighttable th.filename span.filename{margin-top:0}.highlighttable .linenos{background-color:var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-top-left-radius:.1rem;font-size:.85em;padding:.7720588235em 0 .7720588235em 1.1764705882em;-webkit-user-select:none;user-select:none}.highlighttable .linenodiv{box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;padding-right:.5882352941em}.highlighttable .linenodiv pre{color:var(--md-default-fg-color--light);text-align:right}.highlighttable .code{flex:1;min-width:0}.linenodiv a{color:inherit}.md-typeset .highlighttable{direction:ltr;margin:1em 0}.md-typeset .highlighttable>tbody>tr>.code>div>pre>code{border-bottom-left-radius:0;border-top-left-radius:0}.md-typeset .highlight+.result{border:.05rem solid var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top-width:.1rem;margin-top:-1.125em;overflow:visible;padding:0 1em}.md-typeset .highlight+.result:after{clear:both;content:"";display:block}@media screen and (max-width:44.984375em){.md-content__inner>.highlight{margin:1em -.8rem}.md-content__inner>.highlight>.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.code>div>pre>code,.md-content__inner>.highlight>.highlighttable>tbody>tr>.filename span.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.linenos,.md-content__inner>.highlight>pre>code{border-radius:0}.md-content__inner>.highlight+.result{border-left-width:0;border-radius:0;border-right-width:0;margin-left:-.8rem;margin-right:-.8rem}}.md-typeset .keys kbd:after,.md-typeset .keys kbd:before{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;color:inherit;margin:0;position:relative}.md-typeset .keys span{color:var(--md-default-fg-color--light);padding:0 .2em}.md-typeset .keys .key-alt:before,.md-typeset .keys .key-left-alt:before,.md-typeset .keys .key-right-alt:before{content:"โŽ‡";padding-right:.4em}.md-typeset .keys .key-command:before,.md-typeset .keys .key-left-command:before,.md-typeset .keys .key-right-command:before{content:"โŒ˜";padding-right:.4em}.md-typeset .keys .key-control:before,.md-typeset .keys .key-left-control:before,.md-typeset .keys .key-right-control:before{content:"โŒƒ";padding-right:.4em}.md-typeset .keys .key-left-meta:before,.md-typeset .keys .key-meta:before,.md-typeset .keys .key-right-meta:before{content:"โ—†";padding-right:.4em}.md-typeset .keys .key-left-option:before,.md-typeset .keys .key-option:before,.md-typeset .keys .key-right-option:before{content:"โŒฅ";padding-right:.4em}.md-typeset .keys .key-left-shift:before,.md-typeset .keys .key-right-shift:before,.md-typeset .keys .key-shift:before{content:"โ‡ง";padding-right:.4em}.md-typeset .keys .key-left-super:before,.md-typeset .keys .key-right-super:before,.md-typeset .keys .key-super:before{content:"โ–";padding-right:.4em}.md-typeset .keys .key-left-windows:before,.md-typeset .keys .key-right-windows:before,.md-typeset .keys .key-windows:before{content:"โŠž";padding-right:.4em}.md-typeset .keys .key-arrow-down:before{content:"โ†“";padding-right:.4em}.md-typeset .keys .key-arrow-left:before{content:"โ†";padding-right:.4em}.md-typeset .keys .key-arrow-right:before{content:"โ†’";padding-right:.4em}.md-typeset .keys .key-arrow-up:before{content:"โ†‘";padding-right:.4em}.md-typeset .keys .key-backspace:before{content:"โŒซ";padding-right:.4em}.md-typeset .keys .key-backtab:before{content:"โ‡ค";padding-right:.4em}.md-typeset .keys .key-caps-lock:before{content:"โ‡ช";padding-right:.4em}.md-typeset .keys .key-clear:before{content:"โŒง";padding-right:.4em}.md-typeset .keys .key-context-menu:before{content:"โ˜ฐ";padding-right:.4em}.md-typeset .keys .key-delete:before{content:"โŒฆ";padding-right:.4em}.md-typeset .keys .key-eject:before{content:"โ";padding-right:.4em}.md-typeset .keys .key-end:before{content:"โค“";padding-right:.4em}.md-typeset .keys .key-escape:before{content:"โŽ‹";padding-right:.4em}.md-typeset .keys .key-home:before{content:"โค’";padding-right:.4em}.md-typeset .keys .key-insert:before{content:"โŽ€";padding-right:.4em}.md-typeset .keys .key-page-down:before{content:"โ‡Ÿ";padding-right:.4em}.md-typeset .keys .key-page-up:before{content:"โ‡ž";padding-right:.4em}.md-typeset .keys .key-print-screen:before{content:"โŽ™";padding-right:.4em}.md-typeset .keys .key-tab:after{content:"โ‡ฅ";padding-left:.4em}.md-typeset .keys .key-num-enter:after{content:"โŒค";padding-left:.4em}.md-typeset .keys .key-enter:after{content:"โŽ";padding-left:.4em}:root{--md-tabbed-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-tabbed-icon--next:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .tabbed-set{border-radius:.1rem;display:flex;flex-flow:column wrap;margin:1em 0;position:relative}.md-typeset .tabbed-set>input{height:0;opacity:0;position:absolute;width:0}.md-typeset .tabbed-set>input:target{--md-scroll-offset:0.625em}.md-typeset .tabbed-set>input.focus-visible~.tabbed-labels:before{background-color:var(--md-accent-fg-color)}.md-typeset .tabbed-labels{-ms-overflow-style:none;box-shadow:0 -.05rem var(--md-default-fg-color--lightest) inset;display:flex;max-width:100%;overflow:auto;scrollbar-width:none}@media print{.md-typeset .tabbed-labels{display:contents}}@media screen{.js .md-typeset .tabbed-labels{position:relative}.js .md-typeset .tabbed-labels:before{background:var(--md-default-fg-color);bottom:0;content:"";display:block;height:2px;left:0;position:absolute;transform:translateX(var(--md-indicator-x));transition:width 225ms,background-color .25s,transform .25s;transition-timing-function:cubic-bezier(.4,0,.2,1);width:var(--md-indicator-width)}}.md-typeset .tabbed-labels::-webkit-scrollbar{display:none}.md-typeset .tabbed-labels>label{border-bottom:.1rem solid #0000;border-radius:.1rem .1rem 0 0;color:var(--md-default-fg-color--light);cursor:pointer;flex-shrink:0;font-size:.64rem;font-weight:700;padding:.78125em 1.25em .625em;scroll-margin-inline-start:1rem;transition:background-color .25s,color .25s;white-space:nowrap;width:auto}@media print{.md-typeset .tabbed-labels>label:first-child{order:1}.md-typeset .tabbed-labels>label:nth-child(2){order:2}.md-typeset .tabbed-labels>label:nth-child(3){order:3}.md-typeset .tabbed-labels>label:nth-child(4){order:4}.md-typeset .tabbed-labels>label:nth-child(5){order:5}.md-typeset .tabbed-labels>label:nth-child(6){order:6}.md-typeset .tabbed-labels>label:nth-child(7){order:7}.md-typeset .tabbed-labels>label:nth-child(8){order:8}.md-typeset .tabbed-labels>label:nth-child(9){order:9}.md-typeset .tabbed-labels>label:nth-child(10){order:10}.md-typeset .tabbed-labels>label:nth-child(11){order:11}.md-typeset .tabbed-labels>label:nth-child(12){order:12}.md-typeset .tabbed-labels>label:nth-child(13){order:13}.md-typeset .tabbed-labels>label:nth-child(14){order:14}.md-typeset .tabbed-labels>label:nth-child(15){order:15}.md-typeset .tabbed-labels>label:nth-child(16){order:16}.md-typeset .tabbed-labels>label:nth-child(17){order:17}.md-typeset .tabbed-labels>label:nth-child(18){order:18}.md-typeset .tabbed-labels>label:nth-child(19){order:19}.md-typeset .tabbed-labels>label:nth-child(20){order:20}}.md-typeset .tabbed-labels>label:hover{color:var(--md-default-fg-color)}.md-typeset .tabbed-labels>label>[href]:first-child{color:inherit}.md-typeset .tabbed-labels--linked>label{padding:0}.md-typeset .tabbed-labels--linked>label>a{display:block;padding:.78125em 1.25em .625em}.md-typeset .tabbed-content{width:100%}@media print{.md-typeset .tabbed-content{display:contents}}.md-typeset .tabbed-block{display:none}@media print{.md-typeset .tabbed-block{display:block}.md-typeset .tabbed-block:first-child{order:1}.md-typeset .tabbed-block:nth-child(2){order:2}.md-typeset .tabbed-block:nth-child(3){order:3}.md-typeset .tabbed-block:nth-child(4){order:4}.md-typeset .tabbed-block:nth-child(5){order:5}.md-typeset .tabbed-block:nth-child(6){order:6}.md-typeset .tabbed-block:nth-child(7){order:7}.md-typeset .tabbed-block:nth-child(8){order:8}.md-typeset .tabbed-block:nth-child(9){order:9}.md-typeset .tabbed-block:nth-child(10){order:10}.md-typeset .tabbed-block:nth-child(11){order:11}.md-typeset .tabbed-block:nth-child(12){order:12}.md-typeset .tabbed-block:nth-child(13){order:13}.md-typeset .tabbed-block:nth-child(14){order:14}.md-typeset .tabbed-block:nth-child(15){order:15}.md-typeset .tabbed-block:nth-child(16){order:16}.md-typeset .tabbed-block:nth-child(17){order:17}.md-typeset .tabbed-block:nth-child(18){order:18}.md-typeset .tabbed-block:nth-child(19){order:19}.md-typeset .tabbed-block:nth-child(20){order:20}}.md-typeset .tabbed-block>.highlight:first-child>pre,.md-typeset .tabbed-block>pre:first-child{margin:0}.md-typeset .tabbed-block>.highlight:first-child>pre>code,.md-typeset .tabbed-block>pre:first-child>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child>.filename{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable{margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.filename span.filename,.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.linenos{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.code>div>pre>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child+.result{margin-top:-.125em}.md-typeset .tabbed-block>.tabbed-set{margin:0}.md-typeset .tabbed-button{align-self:center;border-radius:100%;color:var(--md-default-fg-color--light);cursor:pointer;display:block;height:.9rem;margin-top:.1rem;pointer-events:auto;transition:background-color .25s;width:.9rem}.md-typeset .tabbed-button:hover{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-typeset .tabbed-button:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-tabbed-icon--prev);mask-image:var(--md-tabbed-icon--prev);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color .25s,transform .25s;width:100%}.md-typeset .tabbed-control{background:linear-gradient(to right,var(--md-default-bg-color) 60%,#0000);display:flex;height:1.9rem;justify-content:start;pointer-events:none;position:absolute;transition:opacity 125ms;width:1.2rem}[dir=rtl] .md-typeset .tabbed-control{transform:rotate(180deg)}.md-typeset .tabbed-control[hidden]{opacity:0}.md-typeset .tabbed-control--next{background:linear-gradient(to left,var(--md-default-bg-color) 60%,#0000);justify-content:end;right:0}.md-typeset .tabbed-control--next .tabbed-button:after{-webkit-mask-image:var(--md-tabbed-icon--next);mask-image:var(--md-tabbed-icon--next)}@media screen and (max-width:44.984375em){[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels{padding-right:.8rem}.md-content__inner>.tabbed-set .tabbed-labels{margin:0 -.8rem;max-width:100vw;scroll-padding-inline-start:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-left:.8rem}.md-content__inner>.tabbed-set .tabbed-labels:after{content:""}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-right:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-left:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-right:-.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{width:2rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-left:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-right:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-left:-.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{width:2rem}}@media screen{.md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){color:var(--md-default-fg-color)}.md-typeset .no-js .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .no-js .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .no-js .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .no-js .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .no-js .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .no-js .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .no-js .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .no-js .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .no-js .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .no-js .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .no-js .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .no-js .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .no-js .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .no-js .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .no-js .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .no-js .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .no-js .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .no-js .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .no-js .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .no-js .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9),.no-js .md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.no-js .md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.no-js .md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.no-js .md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.no-js .md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.no-js .md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.no-js .md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.no-js .md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.no-js .md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.no-js .md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.no-js .md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.no-js .md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.no-js .md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.no-js .md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.no-js .md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.no-js .md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.no-js .md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.no-js .md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.no-js .md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.no-js .md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){border-color:var(--md-default-fg-color)}}.md-typeset .tabbed-set>input:first-child.focus-visible~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10).focus-visible~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11).focus-visible~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12).focus-visible~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13).focus-visible~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14).focus-visible~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15).focus-visible~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16).focus-visible~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17).focus-visible~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18).focus-visible~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19).focus-visible~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2).focus-visible~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20).focus-visible~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3).focus-visible~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4).focus-visible~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5).focus-visible~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6).focus-visible~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7).focus-visible~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8).focus-visible~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9).focus-visible~.tabbed-labels>:nth-child(9){color:var(--md-accent-fg-color)}.md-typeset .tabbed-set>input:first-child:checked~.tabbed-content>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-content>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-content>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-content>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-content>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-content>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-content>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-content>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-content>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-content>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-content>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-content>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-content>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-content>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-content>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-content>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-content>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-content>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-content>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-content>:nth-child(9){display:block}:root{--md-tasklist-icon:url('data:image/svg+xml;charset=utf-8,');--md-tasklist-icon--checked:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .task-list-item{list-style-type:none;position:relative}[dir=ltr] .md-typeset .task-list-item [type=checkbox]{left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em}.md-typeset .task-list-control [type=checkbox]{opacity:0;z-index:-1}[dir=ltr] .md-typeset .task-list-indicator:before{left:-1.5em}[dir=rtl] .md-typeset .task-list-indicator:before{right:-1.5em}.md-typeset .task-list-indicator:before{background-color:var(--md-default-fg-color--lightest);content:"";height:1.25em;-webkit-mask-image:var(--md-tasklist-icon);mask-image:var(--md-tasklist-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.15em;width:1.25em}.md-typeset [type=checkbox]:checked+.task-list-indicator:before{background-color:#00e676;-webkit-mask-image:var(--md-tasklist-icon--checked);mask-image:var(--md-tasklist-icon--checked)}:root>*{--md-mermaid-font-family:var(--md-text-font-family),sans-serif;--md-mermaid-edge-color:var(--md-code-fg-color);--md-mermaid-node-bg-color:var(--md-accent-fg-color--transparent);--md-mermaid-node-fg-color:var(--md-accent-fg-color);--md-mermaid-label-bg-color:var(--md-default-bg-color);--md-mermaid-label-fg-color:var(--md-code-fg-color);--md-mermaid-sequence-actor-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-actor-fg-color:var(--md-mermaid-label-fg-color);--md-mermaid-sequence-actor-border-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-actor-line-color:var(--md-default-fg-color--lighter);--md-mermaid-sequence-actorman-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-actorman-line-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-box-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-box-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-label-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-label-fg-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-loop-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-loop-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-loop-border-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-message-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-message-line-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-note-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-note-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-note-border-color:var(--md-mermaid-label-fg-color);--md-mermaid-sequence-number-bg-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-number-fg-color:var(--md-accent-bg-color)}.mermaid{line-height:normal;margin:1em 0}.md-typeset .grid{grid-gap:.4rem;display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,16rem),1fr));margin:1em 0}.md-typeset .grid.cards>ol,.md-typeset .grid.cards>ul{display:contents}.md-typeset .grid.cards>ol>li,.md-typeset .grid.cards>ul>li,.md-typeset .grid>.card{border:.05rem solid var(--md-default-fg-color--lightest);border-radius:.1rem;display:block;margin:0;padding:.8rem;transition:border .25s,box-shadow .25s}.md-typeset .grid.cards>ol>li:focus-within,.md-typeset .grid.cards>ol>li:hover,.md-typeset .grid.cards>ul>li:focus-within,.md-typeset .grid.cards>ul>li:hover,.md-typeset .grid>.card:focus-within,.md-typeset .grid>.card:hover{border-color:#0000;box-shadow:var(--md-shadow-z2)}.md-typeset .grid.cards>ol>li>hr,.md-typeset .grid.cards>ul>li>hr,.md-typeset .grid>.card>hr{margin-bottom:1em;margin-top:1em}.md-typeset .grid.cards>ol>li>:first-child,.md-typeset .grid.cards>ul>li>:first-child,.md-typeset .grid>.card>:first-child{margin-top:0}.md-typeset .grid.cards>ol>li>:last-child,.md-typeset .grid.cards>ul>li>:last-child,.md-typeset .grid>.card>:last-child{margin-bottom:0}.md-typeset .grid>*,.md-typeset .grid>.admonition,.md-typeset .grid>.highlight>*,.md-typeset .grid>.highlighttable,.md-typeset .grid>.md-typeset details,.md-typeset .grid>details,.md-typeset .grid>pre{margin-bottom:0;margin-top:0}.md-typeset .grid>.highlight>pre:only-child,.md-typeset .grid>.highlight>pre>code,.md-typeset .grid>.highlighttable,.md-typeset .grid>.highlighttable>tbody,.md-typeset .grid>.highlighttable>tbody>tr,.md-typeset .grid>.highlighttable>tbody>tr>.code,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight>pre,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight>pre>code{height:100%}.md-typeset .grid>.tabbed-set{margin-bottom:0;margin-top:0}@media screen and (min-width:45em){[dir=ltr] .md-typeset .inline{float:left}[dir=rtl] .md-typeset .inline{float:right}[dir=ltr] .md-typeset .inline{margin-right:.8rem}[dir=rtl] .md-typeset .inline{margin-left:.8rem}.md-typeset .inline{margin-bottom:.8rem;margin-top:0;width:11.7rem}[dir=ltr] .md-typeset .inline.end{float:right}[dir=rtl] .md-typeset .inline.end{float:left}[dir=ltr] .md-typeset .inline.end{margin-left:.8rem;margin-right:0}[dir=rtl] .md-typeset .inline.end{margin-left:0;margin-right:.8rem}} \ No newline at end of file diff --git a/0.12/assets/stylesheets/main.3cba04c6.min.css.map b/0.12/assets/stylesheets/main.3cba04c6.min.css.map new file mode 100644 index 000000000..0d8f7b6bd --- /dev/null +++ b/0.12/assets/stylesheets/main.3cba04c6.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["src/templates/assets/stylesheets/main/components/_meta.scss","../../../../src/templates/assets/stylesheets/main.scss","src/templates/assets/stylesheets/main/_resets.scss","src/templates/assets/stylesheets/main/_colors.scss","src/templates/assets/stylesheets/main/_icons.scss","src/templates/assets/stylesheets/main/_typeset.scss","src/templates/assets/stylesheets/utilities/_break.scss","src/templates/assets/stylesheets/main/components/_author.scss","src/templates/assets/stylesheets/main/components/_banner.scss","src/templates/assets/stylesheets/main/components/_base.scss","src/templates/assets/stylesheets/main/components/_clipboard.scss","src/templates/assets/stylesheets/main/components/_code.scss","src/templates/assets/stylesheets/main/components/_consent.scss","src/templates/assets/stylesheets/main/components/_content.scss","src/templates/assets/stylesheets/main/components/_dialog.scss","src/templates/assets/stylesheets/main/components/_feedback.scss","src/templates/assets/stylesheets/main/components/_footer.scss","src/templates/assets/stylesheets/main/components/_form.scss","src/templates/assets/stylesheets/main/components/_header.scss","node_modules/material-design-color/material-color.scss","src/templates/assets/stylesheets/main/components/_nav.scss","src/templates/assets/stylesheets/main/components/_pagination.scss","src/templates/assets/stylesheets/main/components/_post.scss","src/templates/assets/stylesheets/main/components/_progress.scss","src/templates/assets/stylesheets/main/components/_search.scss","src/templates/assets/stylesheets/main/components/_select.scss","src/templates/assets/stylesheets/main/components/_sidebar.scss","src/templates/assets/stylesheets/main/components/_source.scss","src/templates/assets/stylesheets/main/components/_status.scss","src/templates/assets/stylesheets/main/components/_tabs.scss","src/templates/assets/stylesheets/main/components/_tag.scss","src/templates/assets/stylesheets/main/components/_tooltip.scss","src/templates/assets/stylesheets/main/components/_tooltip2.scss","src/templates/assets/stylesheets/main/components/_top.scss","src/templates/assets/stylesheets/main/components/_version.scss","src/templates/assets/stylesheets/main/extensions/markdown/_admonition.scss","src/templates/assets/stylesheets/main/extensions/markdown/_footnotes.scss","src/templates/assets/stylesheets/main/extensions/markdown/_toc.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_arithmatex.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_critic.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_details.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_emoji.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_highlight.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_keys.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_tabbed.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_tasklist.scss","src/templates/assets/stylesheets/main/integrations/_mermaid.scss","src/templates/assets/stylesheets/main/modifiers/_grid.scss","src/templates/assets/stylesheets/main/modifiers/_inline.scss"],"names":[],"mappings":"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CJ,CmB9qCI,8CAEE,kCAAA,CAAA,0BnB+qCN,CACF,CKj3CI,0Cc0MA,0CACE,YnB0qCJ,CmBvqCI,yDACE,UnByqCN,CmBrqCI,wDACE,YnBuqCN,CmBnqCI,kDACE,YnBqqCN,CmBhqCE,gBAIE,iDAAA,CADA,gCAAA,CAFA,aAAA,CACA,enBoqCJ,CACF,CK96CM,+DcmRF,6CACE,YnB8pCJ,CmB3pCI,4DACE,UnB6pCN,CmBzpCI,2DACE,YnB2pCN,CmBvpCI,qDACE,YnBypCN,CACF,CKt6CI,mCc7JJ,QAgbI,oBnBupCF,CmBjpCI,kCAME,qCAAA,CACA,qDAAA,CANA,eAAA,CACA,KAAA,CAGA,SnBmpCN,CmB9oCM,6CACE,uBnBgpCR,CmB5oCM,gDACE,YnB8oCR,CmBzoCI,2CACE,kBnB4oCN,CmB7oCI,2CACE,mBnB4oCN,CmB7oCI,iCAEE,oBnB2oCN,CmBpoCI,yDACE,kBnBsoCN,CmBvoCI,yDACE,iBnBsoCN,CACF,CK/7CI,sCc7JJ,QA4dI,oBAAA,CACA,oDnBooCF,CmB9nCI,gCAME,qCAAA,CACA,qDAAA,CANA,eAAA,CACA,KAAA,CAGA,SnBgoCN,CmB3nCM,8CACE,uBnB6nCR,CmBznCM,8CACE,YnB2nCR,CmBtnCI,yCACE,kBnBynCN,CmB1nCI,yCACE,mBnBynCN,CmB1nCI,+BAEE,oBnBwnCN,CmBjnCI,uDACE,kBnBmnCN,CmBpnCI,uDACE,iBnBmnCN,CmB9mCE,wBACE,YAAA,CACA,sBAAA,CAEA,SAAA,CACA,6FACE,CAHF,mBnBknCJ,CmB1mCI,sCACE,enB4mCN,CmBvmCE,iFACE,sBAAA,CAEA,SAAA,CACA,4FACE,CAHF,kBnB2mCJ,CmBlmCE,iDACE,enBomCJ,CmBhmCE,6CACE,YnBkmCJ,CmB9lCE,uBACE,aAAA,CACA,enBgmCJ,CmB7lCI,kCACE,enB+lCN,CmB3lCI,qCACE,enB6lCN,CmB1lCM,0CACE,uCnB4lCR,CmBxlCM,6DACE,mBnB0lCR,CmBtlCM,yFAEE,YnBwlCR,CmBnlCI,yCAEE,kBnBulCN,CmBzlCI,yCAEE,mBnBulCN,CmBzlCI,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","file":"main.css"} \ No newline at end of file diff --git a/0.12/assets/stylesheets/main.6543a935.min.css b/0.12/assets/stylesheets/main.6543a935.min.css deleted file mode 100644 index f9f772d1c..000000000 --- a/0.12/assets/stylesheets/main.6543a935.min.css +++ /dev/null @@ -1 +0,0 @@ -@charset "UTF-8";html{-webkit-text-size-adjust:none;-moz-text-size-adjust:none;text-size-adjust:none;box-sizing:border-box}*,:after,:before{box-sizing:inherit}@media (prefers-reduced-motion){*,:after,:before{transition:none!important}}body{margin:0}a,button,input,label{-webkit-tap-highlight-color:transparent}a{color:inherit;text-decoration:none}hr{border:0;box-sizing:initial;display:block;height:.05rem;overflow:visible;padding:0}small{font-size:80%}sub,sup{line-height:1em}img{border-style:none}table{border-collapse:initial;border-spacing:0}td,th{font-weight:400;vertical-align:top}button{background:#0000;border:0;font-family:inherit;font-size:inherit;margin:0;padding:0}input{border:0;outline:none}:root{--md-primary-fg-color:#4051b5;--md-primary-fg-color--light:#5d6cc0;--md-primary-fg-color--dark:#303fa1;--md-primary-bg-color:#fff;--md-primary-bg-color--light:#ffffffb3;--md-accent-fg-color:#526cfe;--md-accent-fg-color--transparent:#526cfe1a;--md-accent-bg-color:#fff;--md-accent-bg-color--light:#ffffffb3}[data-md-color-scheme=default]{color-scheme:light}[data-md-color-scheme=default] img[src$="#gh-dark-mode-only"],[data-md-color-scheme=default] img[src$="#only-dark"]{display:none}:root,[data-md-color-scheme=default]{--md-hue:225deg;--md-default-fg-color:#000000de;--md-default-fg-color--light:#0000008a;--md-default-fg-color--lighter:#00000052;--md-default-fg-color--lightest:#00000012;--md-default-bg-color:#fff;--md-default-bg-color--light:#ffffffb3;--md-default-bg-color--lighter:#ffffff4d;--md-default-bg-color--lightest:#ffffff1f;--md-code-fg-color:#36464e;--md-code-bg-color:#f5f5f5;--md-code-hl-color:#4287ff;--md-code-hl-color--light:#4287ff1a;--md-code-hl-number-color:#d52a2a;--md-code-hl-special-color:#db1457;--md-code-hl-function-color:#a846b9;--md-code-hl-constant-color:#6e59d9;--md-code-hl-keyword-color:#3f6ec6;--md-code-hl-string-color:#1c7d4d;--md-code-hl-name-color:var(--md-code-fg-color);--md-code-hl-operator-color:var(--md-default-fg-color--light);--md-code-hl-punctuation-color:var(--md-default-fg-color--light);--md-code-hl-comment-color:var(--md-default-fg-color--light);--md-code-hl-generic-color:var(--md-default-fg-color--light);--md-code-hl-variable-color:var(--md-default-fg-color--light);--md-typeset-color:var(--md-default-fg-color);--md-typeset-a-color:var(--md-primary-fg-color);--md-typeset-del-color:#f5503d26;--md-typeset-ins-color:#0bd57026;--md-typeset-kbd-color:#fafafa;--md-typeset-kbd-accent-color:#fff;--md-typeset-kbd-border-color:#b8b8b8;--md-typeset-mark-color:#ffff0080;--md-typeset-table-color:#0000001f;--md-typeset-table-color--light:rgba(0,0,0,.035);--md-admonition-fg-color:var(--md-default-fg-color);--md-admonition-bg-color:var(--md-default-bg-color);--md-warning-fg-color:#000000de;--md-warning-bg-color:#ff9;--md-footer-fg-color:#fff;--md-footer-fg-color--light:#ffffffb3;--md-footer-fg-color--lighter:#ffffff73;--md-footer-bg-color:#000000de;--md-footer-bg-color--dark:#00000052;--md-shadow-z1:0 0.2rem 0.5rem #0000000d,0 0 0.05rem #0000001a;--md-shadow-z2:0 0.2rem 0.5rem #0000001a,0 0 0.05rem #00000040;--md-shadow-z3:0 0.2rem 0.5rem #0003,0 0 0.05rem #00000059}.md-icon svg{fill:currentcolor;display:block;height:1.2rem;width:1.2rem}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--md-text-font-family:var(--md-text-font,_),-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;--md-code-font-family:var(--md-code-font,_),SFMono-Regular,Consolas,Menlo,monospace}aside,body,input{font-feature-settings:"kern","liga";color:var(--md-typeset-color);font-family:var(--md-text-font-family)}code,kbd,pre{font-feature-settings:"kern";font-family:var(--md-code-font-family)}:root{--md-typeset-table-sort-icon:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--asc:url('data:image/svg+xml;charset=utf-8,');--md-typeset-table-sort-icon--desc:url('data:image/svg+xml;charset=utf-8,')}.md-typeset{-webkit-print-color-adjust:exact;color-adjust:exact;font-size:.8rem;line-height:1.6}@media print{.md-typeset{font-size:.68rem}}.md-typeset blockquote,.md-typeset dl,.md-typeset figure,.md-typeset ol,.md-typeset pre,.md-typeset ul{margin-bottom:1em;margin-top:1em}.md-typeset h1{color:var(--md-default-fg-color--light);font-size:2em;line-height:1.3;margin:0 0 1.25em}.md-typeset h1,.md-typeset h2{font-weight:300;letter-spacing:-.01em}.md-typeset h2{font-size:1.5625em;line-height:1.4;margin:1.6em 0 .64em}.md-typeset h3{font-size:1.25em;font-weight:400;letter-spacing:-.01em;line-height:1.5;margin:1.6em 0 .8em}.md-typeset h2+h3{margin-top:.8em}.md-typeset h4{font-weight:700;letter-spacing:-.01em;margin:1em 0}.md-typeset h5,.md-typeset h6{color:var(--md-default-fg-color--light);font-size:.8em;font-weight:700;letter-spacing:-.01em;margin:1.25em 0}.md-typeset h5{text-transform:uppercase}.md-typeset hr{border-bottom:.05rem solid var(--md-default-fg-color--lightest);display:flow-root;margin:1.5em 0}.md-typeset a{color:var(--md-typeset-a-color);word-break:break-word}.md-typeset a,.md-typeset a:before{transition:color 125ms}.md-typeset a:focus,.md-typeset a:hover{color:var(--md-accent-fg-color)}.md-typeset a:focus code,.md-typeset a:hover code{background-color:var(--md-accent-fg-color--transparent)}.md-typeset a code{color:currentcolor;transition:background-color 125ms}.md-typeset a.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset code,.md-typeset kbd,.md-typeset pre{color:var(--md-code-fg-color);direction:ltr;font-variant-ligatures:none}@media print{.md-typeset code,.md-typeset kbd,.md-typeset pre{white-space:pre-wrap}}.md-typeset code{background-color:var(--md-code-bg-color);border-radius:.1rem;-webkit-box-decoration-break:clone;box-decoration-break:clone;font-size:.85em;padding:0 .2941176471em;word-break:break-word}.md-typeset code:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-typeset pre{display:flow-root;line-height:1.4;position:relative}.md-typeset pre>code{-webkit-box-decoration-break:slice;box-decoration-break:slice;box-shadow:none;display:block;margin:0;outline-color:var(--md-accent-fg-color);overflow:auto;padding:.7720588235em 1.1764705882em;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin;touch-action:auto;word-break:normal}.md-typeset pre>code:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-typeset pre>code::-webkit-scrollbar{height:.2rem;width:.2rem}.md-typeset pre>code::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-typeset pre>code::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}.md-typeset kbd{background-color:var(--md-typeset-kbd-color);border-radius:.1rem;box-shadow:0 .1rem 0 .05rem var(--md-typeset-kbd-border-color),0 .1rem 0 var(--md-typeset-kbd-border-color),0 -.1rem .2rem var(--md-typeset-kbd-accent-color) inset;color:var(--md-default-fg-color);display:inline-block;font-size:.75em;padding:0 .6666666667em;vertical-align:text-top;word-break:break-word}.md-typeset mark{background-color:var(--md-typeset-mark-color);-webkit-box-decoration-break:clone;box-decoration-break:clone;color:inherit;word-break:break-word}.md-typeset abbr{border-bottom:.05rem dotted var(--md-default-fg-color--light);cursor:help;text-decoration:none}.md-typeset small{opacity:.75}[dir=ltr] .md-typeset sub,[dir=ltr] .md-typeset sup{margin-left:.078125em}[dir=rtl] .md-typeset sub,[dir=rtl] .md-typeset sup{margin-right:.078125em}[dir=ltr] .md-typeset blockquote{padding-left:.6rem}[dir=rtl] .md-typeset blockquote{padding-right:.6rem}[dir=ltr] .md-typeset blockquote{border-left:.2rem solid var(--md-default-fg-color--lighter)}[dir=rtl] .md-typeset blockquote{border-right:.2rem solid var(--md-default-fg-color--lighter)}.md-typeset blockquote{color:var(--md-default-fg-color--light);margin-left:0;margin-right:0}.md-typeset ul{list-style-type:disc}[dir=ltr] .md-typeset ol,[dir=ltr] .md-typeset ul{margin-left:.625em}[dir=rtl] .md-typeset ol,[dir=rtl] .md-typeset ul{margin-right:.625em}.md-typeset ol,.md-typeset ul{padding:0}.md-typeset ol:not([hidden]),.md-typeset ul:not([hidden]){display:flow-root}.md-typeset ol ol,.md-typeset ul ol{list-style-type:lower-alpha}.md-typeset ol ol ol,.md-typeset ul ol ol{list-style-type:lower-roman}[dir=ltr] .md-typeset ol li,[dir=ltr] .md-typeset ul li{margin-left:1.25em}[dir=rtl] .md-typeset ol li,[dir=rtl] .md-typeset ul li{margin-right:1.25em}.md-typeset ol li,.md-typeset ul li{margin-bottom:.5em}.md-typeset ol li blockquote,.md-typeset ol li p,.md-typeset ul li blockquote,.md-typeset ul li p{margin:.5em 0}.md-typeset ol li:last-child,.md-typeset ul li:last-child{margin-bottom:0}[dir=ltr] .md-typeset ol li ol,[dir=ltr] .md-typeset ol li ul,[dir=ltr] .md-typeset ul li ol,[dir=ltr] .md-typeset ul li ul{margin-left:.625em}[dir=rtl] .md-typeset ol li ol,[dir=rtl] .md-typeset ol li ul,[dir=rtl] .md-typeset ul li ol,[dir=rtl] .md-typeset ul li ul{margin-right:.625em}.md-typeset ol li ol,.md-typeset ol li ul,.md-typeset ul li ol,.md-typeset ul li ul{margin-bottom:.5em;margin-top:.5em}[dir=ltr] .md-typeset dd{margin-left:1.875em}[dir=rtl] .md-typeset dd{margin-right:1.875em}.md-typeset dd{margin-bottom:1.5em;margin-top:1em}.md-typeset img,.md-typeset svg,.md-typeset video{height:auto;max-width:100%}.md-typeset img[align=left]{margin:1em 1em 1em 0}.md-typeset img[align=right]{margin:1em 0 1em 1em}.md-typeset img[align]:only-child{margin-top:0}.md-typeset figure{display:flow-root;margin:1em auto;max-width:100%;text-align:center;width:-moz-fit-content;width:fit-content}.md-typeset figure img{display:block;margin:0 auto}.md-typeset figcaption{font-style:italic;margin:1em auto;max-width:24rem}.md-typeset iframe{max-width:100%}.md-typeset table:not([class]){background-color:var(--md-default-bg-color);border:.05rem solid var(--md-typeset-table-color);border-radius:.1rem;display:inline-block;font-size:.64rem;max-width:100%;overflow:auto;touch-action:auto}@media print{.md-typeset table:not([class]){display:table}}.md-typeset table:not([class])+*{margin-top:1.5em}.md-typeset table:not([class]) td>:first-child,.md-typeset table:not([class]) th>:first-child{margin-top:0}.md-typeset table:not([class]) td>:last-child,.md-typeset table:not([class]) th>:last-child{margin-bottom:0}.md-typeset table:not([class]) td:not([align]),.md-typeset table:not([class]) th:not([align]){text-align:left}[dir=rtl] .md-typeset table:not([class]) td:not([align]),[dir=rtl] .md-typeset table:not([class]) th:not([align]){text-align:right}.md-typeset table:not([class]) th{font-weight:700;min-width:5rem;padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) td{border-top:.05rem solid var(--md-typeset-table-color);padding:.9375em 1.25em;vertical-align:top}.md-typeset table:not([class]) tbody tr{transition:background-color 125ms}.md-typeset table:not([class]) tbody tr:hover{background-color:var(--md-typeset-table-color--light);box-shadow:0 .05rem 0 var(--md-default-bg-color) inset}.md-typeset table:not([class]) a{word-break:normal}.md-typeset table th[role=columnheader]{cursor:pointer}[dir=ltr] .md-typeset table th[role=columnheader]:after{margin-left:.5em}[dir=rtl] .md-typeset table th[role=columnheader]:after{margin-right:.5em}.md-typeset table th[role=columnheader]:after{content:"";display:inline-block;height:1.2em;-webkit-mask-image:var(--md-typeset-table-sort-icon);mask-image:var(--md-typeset-table-sort-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color 125ms;vertical-align:text-bottom;width:1.2em}.md-typeset table th[role=columnheader]:hover:after{background-color:var(--md-default-fg-color--lighter)}.md-typeset table th[role=columnheader][aria-sort=ascending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--asc);mask-image:var(--md-typeset-table-sort-icon--asc)}.md-typeset table th[role=columnheader][aria-sort=descending]:after{background-color:var(--md-default-fg-color--light);-webkit-mask-image:var(--md-typeset-table-sort-icon--desc);mask-image:var(--md-typeset-table-sort-icon--desc)}.md-typeset__scrollwrap{margin:1em -.8rem;overflow-x:auto;touch-action:auto}.md-typeset__table{display:inline-block;margin-bottom:.5em;padding:0 .8rem}@media print{.md-typeset__table{display:block}}html .md-typeset__table table{display:table;margin:0;overflow:hidden;width:100%}@media screen and (max-width:44.984375em){.md-content__inner>pre{margin:1em -.8rem}.md-content__inner>pre code{border-radius:0}}.md-typeset .md-author{border-radius:100%;display:block;flex-shrink:0;height:1.6rem;overflow:hidden;position:relative;transition:color 125ms,transform 125ms;width:1.6rem}.md-typeset .md-author img{display:block}.md-typeset .md-author--more{background:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--lighter);font-size:.6rem;font-weight:700;line-height:1.6rem;text-align:center}.md-typeset .md-author--long{height:2.4rem;width:2.4rem}.md-typeset a.md-author{transform:scale(1)}.md-typeset a.md-author img{border-radius:100%;filter:grayscale(100%) opacity(75%);transition:filter 125ms}.md-typeset a.md-author:focus,.md-typeset a.md-author:hover{transform:scale(1.1);z-index:1}.md-typeset a.md-author:focus img,.md-typeset a.md-author:hover img{filter:grayscale(0)}.md-banner{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color);overflow:auto}@media print{.md-banner{display:none}}.md-banner--warning{background-color:var(--md-warning-bg-color);color:var(--md-warning-fg-color)}.md-banner__inner{font-size:.7rem;margin:.6rem auto;padding:0 .8rem}[dir=ltr] .md-banner__button{float:right}[dir=rtl] .md-banner__button{float:left}.md-banner__button{color:inherit;cursor:pointer;transition:opacity .25s}.no-js .md-banner__button{display:none}.md-banner__button:hover{opacity:.7}html{font-size:125%;height:100%;overflow-x:hidden}@media screen and (min-width:100em){html{font-size:137.5%}}@media screen and (min-width:125em){html{font-size:150%}}body{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;font-size:.5rem;min-height:100%;position:relative;width:100%}@media print{body{display:block}}@media screen and (max-width:59.984375em){body[data-md-scrolllock]{position:fixed}}.md-grid{margin-left:auto;margin-right:auto;max-width:61rem}.md-container{display:flex;flex-direction:column;flex-grow:1}@media print{.md-container{display:block}}.md-main{flex-grow:1}.md-main__inner{display:flex;height:100%;margin-top:1.5rem}.md-ellipsis{overflow:hidden;text-overflow:ellipsis}.md-toggle{display:none}.md-option{height:0;opacity:0;position:absolute;width:0}.md-option:checked+label:not([hidden]){display:block}.md-option.focus-visible+label{outline-color:var(--md-accent-fg-color);outline-style:auto}.md-skip{background-color:var(--md-default-fg-color);border-radius:.1rem;color:var(--md-default-bg-color);font-size:.64rem;margin:.5rem;opacity:0;outline-color:var(--md-accent-fg-color);padding:.3rem .5rem;position:fixed;transform:translateY(.4rem);z-index:-1}.md-skip:focus{opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 175ms 75ms;z-index:10}@page{margin:25mm}:root{--md-clipboard-icon:url('data:image/svg+xml;charset=utf-8,')}.md-clipboard{border-radius:.1rem;color:var(--md-default-fg-color--lightest);cursor:pointer;height:1.5em;outline-color:var(--md-accent-fg-color);outline-offset:.1rem;position:absolute;right:.5em;top:.5em;transition:color .25s;width:1.5em;z-index:1}@media print{.md-clipboard{display:none}}.md-clipboard:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}:hover>.md-clipboard{color:var(--md-default-fg-color--light)}.md-clipboard:focus,.md-clipboard:hover{color:var(--md-accent-fg-color)}.md-clipboard:after{background-color:currentcolor;content:"";display:block;height:1.125em;margin:0 auto;-webkit-mask-image:var(--md-clipboard-icon);mask-image:var(--md-clipboard-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:1.125em}.md-clipboard--inline{cursor:pointer}.md-clipboard--inline code{transition:color .25s,background-color .25s}.md-clipboard--inline:focus code,.md-clipboard--inline:hover code{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-typeset .md-code__content{display:grid}@keyframes consent{0%{opacity:0;transform:translateY(100%)}to{opacity:1;transform:translateY(0)}}@keyframes overlay{0%{opacity:0}to{opacity:1}}.md-consent__overlay{animation:overlay .25s both;-webkit-backdrop-filter:blur(.1rem);backdrop-filter:blur(.1rem);background-color:#0000008a;height:100%;opacity:1;position:fixed;top:0;width:100%;z-index:5}.md-consent__inner{animation:consent .5s cubic-bezier(.1,.7,.1,1) both;background-color:var(--md-default-bg-color);border:0;border-radius:.1rem;bottom:0;box-shadow:0 0 .2rem #0000001a,0 .2rem .4rem #0003;max-height:100%;overflow:auto;padding:0;position:fixed;width:100%;z-index:5}.md-consent__form{padding:.8rem}.md-consent__settings{display:none;margin:1em 0}input:checked+.md-consent__settings{display:block}.md-consent__controls{margin-bottom:.8rem}.md-typeset .md-consent__controls .md-button{display:inline}@media screen and (max-width:44.984375em){.md-typeset .md-consent__controls .md-button{display:block;margin-top:.4rem;text-align:center;width:100%}}.md-consent label{cursor:pointer}.md-content{flex-grow:1;min-width:0}.md-content__inner{margin:0 .8rem 1.2rem;padding-top:.6rem}@media screen and (min-width:76.25em){[dir=ltr] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}[dir=ltr] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner,[dir=rtl] .md-sidebar--primary:not([hidden])~.md-content>.md-content__inner{margin-right:1.2rem}[dir=rtl] .md-sidebar--secondary:not([hidden])~.md-content>.md-content__inner{margin-left:1.2rem}}.md-content__inner:before{content:"";display:block;height:.4rem}.md-content__inner>:last-child{margin-bottom:0}[dir=ltr] .md-content__button{float:right}[dir=rtl] .md-content__button{float:left}[dir=ltr] .md-content__button{margin-left:.4rem}[dir=rtl] .md-content__button{margin-right:.4rem}.md-content__button{margin:.4rem 0;padding:0}@media print{.md-content__button{display:none}}.md-typeset .md-content__button{color:var(--md-default-fg-color--lighter)}.md-content__button svg{display:inline;vertical-align:top}[dir=rtl] .md-content__button svg{transform:scaleX(-1)}[dir=ltr] .md-dialog{right:.8rem}[dir=rtl] .md-dialog{left:.8rem}.md-dialog{background-color:var(--md-default-fg-color);border-radius:.1rem;bottom:.8rem;box-shadow:var(--md-shadow-z3);min-width:11.1rem;opacity:0;padding:.4rem .6rem;pointer-events:none;position:fixed;transform:translateY(100%);transition:transform 0ms .4s,opacity .4s;z-index:4}@media print{.md-dialog{display:none}}.md-dialog--active{opacity:1;pointer-events:auto;transform:translateY(0);transition:transform .4s cubic-bezier(.075,.85,.175,1),opacity .4s}.md-dialog__inner{color:var(--md-default-bg-color);font-size:.7rem}.md-feedback{margin:2em 0 1em;text-align:center}.md-feedback fieldset{border:none;margin:0;padding:0}.md-feedback__title{font-weight:700;margin:1em auto}.md-feedback__inner{position:relative}.md-feedback__list{display:flex;flex-wrap:wrap;place-content:baseline center;position:relative}.md-feedback__list:hover .md-icon:not(:disabled){color:var(--md-default-fg-color--lighter)}:disabled .md-feedback__list{min-height:1.8rem}.md-feedback__icon{color:var(--md-default-fg-color--light);cursor:pointer;flex-shrink:0;margin:0 .1rem;transition:color 125ms}.md-feedback__icon:not(:disabled).md-icon:hover{color:var(--md-accent-fg-color)}.md-feedback__icon:disabled{color:var(--md-default-fg-color--lightest);pointer-events:none}.md-feedback__note{opacity:0;position:relative;transform:translateY(.4rem);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-feedback__note>*{margin:0 auto;max-width:16rem}:disabled .md-feedback__note{opacity:1;transform:translateY(0)}.md-footer{background-color:var(--md-footer-bg-color);color:var(--md-footer-fg-color)}@media print{.md-footer{display:none}}.md-footer__inner{justify-content:space-between;overflow:auto;padding:.2rem}.md-footer__inner:not([hidden]){display:flex}.md-footer__link{align-items:end;display:flex;flex-grow:0.01;margin-bottom:.4rem;margin-top:1rem;max-width:100%;outline-color:var(--md-accent-fg-color);overflow:hidden;transition:opacity .25s}.md-footer__link:focus,.md-footer__link:hover{opacity:.7}[dir=rtl] .md-footer__link svg{transform:scaleX(-1)}@media screen and (max-width:44.984375em){.md-footer__link--prev{flex-shrink:0}.md-footer__link--prev .md-footer__title{display:none}}[dir=ltr] .md-footer__link--next{margin-left:auto}[dir=rtl] .md-footer__link--next{margin-right:auto}.md-footer__link--next{text-align:right}[dir=rtl] .md-footer__link--next{text-align:left}.md-footer__title{flex-grow:1;font-size:.9rem;margin-bottom:.7rem;max-width:calc(100% - 2.4rem);padding:0 1rem;white-space:nowrap}.md-footer__button{margin:.2rem;padding:.4rem}.md-footer__direction{font-size:.64rem;opacity:.7}.md-footer-meta{background-color:var(--md-footer-bg-color--dark)}.md-footer-meta__inner{display:flex;flex-wrap:wrap;justify-content:space-between;padding:.2rem}html .md-footer-meta.md-typeset a{color:var(--md-footer-fg-color--light)}html .md-footer-meta.md-typeset a:focus,html .md-footer-meta.md-typeset a:hover{color:var(--md-footer-fg-color)}.md-copyright{color:var(--md-footer-fg-color--lighter);font-size:.64rem;margin:auto .6rem;padding:.4rem 0;width:100%}@media screen and (min-width:45em){.md-copyright{width:auto}}.md-copyright__highlight{color:var(--md-footer-fg-color--light)}.md-social{display:inline-flex;gap:.2rem;margin:0 .4rem;padding:.2rem 0 .6rem}@media screen and (min-width:45em){.md-social{padding:.6rem 0}}.md-social__link{display:inline-block;height:1.6rem;text-align:center;width:1.6rem}.md-social__link:before{line-height:1.9}.md-social__link svg{fill:currentcolor;max-height:.8rem;vertical-align:-25%}.md-typeset .md-button{border:.1rem solid;border-radius:.1rem;color:var(--md-primary-fg-color);cursor:pointer;display:inline-block;font-weight:700;padding:.625em 2em;transition:color 125ms,background-color 125ms,border-color 125ms}.md-typeset .md-button--primary{background-color:var(--md-primary-fg-color);border-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color)}.md-typeset .md-button:focus,.md-typeset .md-button:hover{background-color:var(--md-accent-fg-color);border-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[dir=ltr] .md-typeset .md-input{border-top-left-radius:.1rem}[dir=ltr] .md-typeset .md-input,[dir=rtl] .md-typeset .md-input{border-top-right-radius:.1rem}[dir=rtl] .md-typeset .md-input{border-top-left-radius:.1rem}.md-typeset .md-input{border-bottom:.1rem solid var(--md-default-fg-color--lighter);box-shadow:var(--md-shadow-z1);font-size:.8rem;height:1.8rem;padding:0 .6rem;transition:border .25s,box-shadow .25s}.md-typeset .md-input:focus,.md-typeset .md-input:hover{border-bottom-color:var(--md-accent-fg-color);box-shadow:var(--md-shadow-z2)}.md-typeset .md-input--stretch{width:100%}.md-header{background-color:var(--md-primary-fg-color);box-shadow:0 0 .2rem #0000,0 .2rem .4rem #0000;color:var(--md-primary-bg-color);display:block;left:0;position:sticky;right:0;top:0;z-index:4}@media print{.md-header{display:none}}.md-header[hidden]{transform:translateY(-100%);transition:transform .25s cubic-bezier(.8,0,.6,1),box-shadow .25s}.md-header--shadow{box-shadow:0 0 .2rem #0000001a,0 .2rem .4rem #0003;transition:transform .25s cubic-bezier(.1,.7,.1,1),box-shadow .25s}.md-header__inner{align-items:center;display:flex;padding:0 .2rem}.md-header__button{color:currentcolor;cursor:pointer;margin:.2rem;outline-color:var(--md-accent-fg-color);padding:.4rem;position:relative;transition:opacity .25s;vertical-align:middle;z-index:1}.md-header__button:hover{opacity:.7}.md-header__button:not([hidden]){display:inline-block}.md-header__button:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}.md-header__button.md-logo{margin:.2rem;padding:.4rem}@media screen and (max-width:76.234375em){.md-header__button.md-logo{display:none}}.md-header__button.md-logo img,.md-header__button.md-logo svg{fill:currentcolor;display:block;height:1.2rem;width:auto}@media screen and (min-width:60em){.md-header__button[for=__search]{display:none}}.no-js .md-header__button[for=__search]{display:none}[dir=rtl] .md-header__button[for=__search] svg{transform:scaleX(-1)}@media screen and (min-width:76.25em){.md-header__button[for=__drawer]{display:none}}.md-header__topic{display:flex;max-width:100%;position:absolute;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;white-space:nowrap}.md-header__topic+.md-header__topic{opacity:0;pointer-events:none;transform:translateX(1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__topic+.md-header__topic{transform:translateX(-1.25rem)}.md-header__topic:first-child{font-weight:700}[dir=ltr] .md-header__title{margin-left:1rem;margin-right:.4rem}[dir=rtl] .md-header__title{margin-left:.4rem;margin-right:1rem}.md-header__title{flex-grow:1;font-size:.9rem;height:2.4rem;line-height:2.4rem}.md-header__title--active .md-header__topic{opacity:0;pointer-events:none;transform:translateX(-1.25rem);transition:transform .4s cubic-bezier(1,.7,.1,.1),opacity .15s;z-index:-1}[dir=rtl] .md-header__title--active .md-header__topic{transform:translateX(1.25rem)}.md-header__title--active .md-header__topic+.md-header__topic{opacity:1;pointer-events:auto;transform:translateX(0);transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .15s;z-index:0}.md-header__title>.md-header__ellipsis{height:100%;position:relative;width:100%}.md-header__option{display:flex;flex-shrink:0;max-width:100%;transition:max-width 0ms .25s,opacity .25s .25s;white-space:nowrap}[data-md-toggle=search]:checked~.md-header .md-header__option{max-width:0;opacity:0;transition:max-width 0ms,opacity 0ms}.md-header__option>input{bottom:0}.md-header__source{display:none}@media screen and (min-width:60em){[dir=ltr] .md-header__source{margin-left:1rem}[dir=rtl] .md-header__source{margin-right:1rem}.md-header__source{display:block;max-width:11.7rem;width:11.7rem}}@media screen and (min-width:76.25em){[dir=ltr] .md-header__source{margin-left:1.4rem}[dir=rtl] .md-header__source{margin-right:1.4rem}}.md-meta{color:var(--md-default-fg-color--light);font-size:.7rem;line-height:1.3}.md-meta__list{display:inline-flex;flex-wrap:wrap;list-style:none;margin:0;padding:0}.md-meta__item:not(:last-child):after{content:"ยท";margin-left:.2rem;margin-right:.2rem}.md-meta__link{color:var(--md-typeset-a-color)}.md-meta__link:focus,.md-meta__link:hover{color:var(--md-accent-fg-color)}.md-draft{background-color:#ff1744;border-radius:.125em;color:#fff;display:inline-block;font-weight:700;padding-left:.5714285714em;padding-right:.5714285714em}:root{--md-nav-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-nav-icon--next:url('data:image/svg+xml;charset=utf-8,');--md-toc-icon:url('data:image/svg+xml;charset=utf-8,')}.md-nav{font-size:.7rem;line-height:1.3}.md-nav__title{color:var(--md-default-fg-color--light);display:block;font-weight:700;overflow:hidden;padding:0 .6rem;text-overflow:ellipsis}.md-nav__title .md-nav__button{display:none}.md-nav__title .md-nav__button img{height:100%;width:auto}.md-nav__title .md-nav__button.md-logo img,.md-nav__title .md-nav__button.md-logo svg{fill:currentcolor;display:block;height:2.4rem;max-width:100%;object-fit:contain;width:auto}.md-nav__list{list-style:none;margin:0;padding:0}.md-nav__link{align-items:flex-start;display:flex;gap:.4rem;margin-top:.625em;scroll-snap-align:start;transition:color 125ms}.md-nav__link--passed{color:var(--md-default-fg-color--light)}.md-nav__item .md-nav__link--active,.md-nav__item .md-nav__link--active code{color:var(--md-typeset-a-color)}.md-nav__link .md-ellipsis{position:relative}[dir=ltr] .md-nav__link .md-icon:last-child{margin-left:auto}[dir=rtl] .md-nav__link .md-icon:last-child{margin-right:auto}.md-nav__link svg{fill:currentcolor;flex-shrink:0;height:1.3em}.md-nav__link[for]:focus,.md-nav__link[for]:hover,.md-nav__link[href]:focus,.md-nav__link[href]:hover{color:var(--md-accent-fg-color);cursor:pointer}.md-nav__link.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-nav--primary .md-nav__link[for=__toc]{display:none}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{background-color:currentcolor;display:block;height:100%;-webkit-mask-image:var(--md-toc-icon);mask-image:var(--md-toc-icon);width:100%}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:none}.md-nav__container>.md-nav__link{margin-top:0}.md-nav__container>.md-nav__link:first-child{flex-grow:1;min-width:0}.md-nav__icon{flex-shrink:0}.md-nav__source{display:none}@media screen and (max-width:76.234375em){.md-nav--primary,.md-nav--primary .md-nav{background-color:var(--md-default-bg-color);display:flex;flex-direction:column;height:100%;left:0;position:absolute;right:0;top:0;z-index:1}.md-nav--primary .md-nav__item,.md-nav--primary .md-nav__title{font-size:.8rem;line-height:1.5}.md-nav--primary .md-nav__title{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);cursor:pointer;height:5.6rem;line-height:2.4rem;padding:3rem .8rem .2rem;position:relative;white-space:nowrap}[dir=ltr] .md-nav--primary .md-nav__title .md-nav__icon{left:.4rem}[dir=rtl] .md-nav--primary .md-nav__title .md-nav__icon{right:.4rem}.md-nav--primary .md-nav__title .md-nav__icon{display:block;height:1.2rem;margin:.2rem;position:absolute;top:.4rem;width:1.2rem}.md-nav--primary .md-nav__title .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--prev);mask-image:var(--md-nav-icon--prev);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}.md-nav--primary .md-nav__title~.md-nav__list{background-color:var(--md-default-bg-color);box-shadow:0 .05rem 0 var(--md-default-fg-color--lightest) inset;overflow-y:auto;scroll-snap-type:y mandatory;touch-action:pan-y}.md-nav--primary .md-nav__title~.md-nav__list>:first-child{border-top:0}.md-nav--primary .md-nav__title[for=__drawer]{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);font-weight:700}.md-nav--primary .md-nav__title .md-logo{display:block;left:.2rem;margin:.2rem;padding:.4rem;position:absolute;right:.2rem;top:.2rem}.md-nav--primary .md-nav__list{flex:1}.md-nav--primary .md-nav__item{border-top:.05rem solid var(--md-default-fg-color--lightest)}.md-nav--primary .md-nav__item--active>.md-nav__link{color:var(--md-typeset-a-color)}.md-nav--primary .md-nav__item--active>.md-nav__link:focus,.md-nav--primary .md-nav__item--active>.md-nav__link:hover{color:var(--md-accent-fg-color)}.md-nav--primary .md-nav__link{margin-top:0;padding:.6rem .8rem}.md-nav--primary .md-nav__link svg{margin-top:.1em}.md-nav--primary .md-nav__link>.md-nav__link{padding:0}[dir=ltr] .md-nav--primary .md-nav__link .md-nav__icon{margin-right:-.2rem}[dir=rtl] .md-nav--primary .md-nav__link .md-nav__icon{margin-left:-.2rem}.md-nav--primary .md-nav__link .md-nav__icon{font-size:1.2rem;height:1.2rem;width:1.2rem}.md-nav--primary .md-nav__link .md-nav__icon:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-nav--primary .md-nav__icon:after{transform:scale(-1)}.md-nav--primary .md-nav--secondary .md-nav{background-color:initial;position:static}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-left:1.4rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav__link{padding-right:1.4rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-left:2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav__link{padding-right:2rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-left:2.6rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav__link{padding-right:2.6rem}[dir=ltr] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-left:3.2rem}[dir=rtl] .md-nav--primary .md-nav--secondary .md-nav .md-nav .md-nav .md-nav .md-nav__link{padding-right:3.2rem}.md-nav--secondary{background-color:initial}.md-nav__toggle~.md-nav{display:flex;opacity:0;transform:translateX(100%);transition:transform .25s cubic-bezier(.8,0,.6,1),opacity 125ms 50ms}[dir=rtl] .md-nav__toggle~.md-nav{transform:translateX(-100%)}.md-nav__toggle:checked~.md-nav{opacity:1;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),opacity 125ms 125ms}.md-nav__toggle:checked~.md-nav>.md-nav__list{-webkit-backface-visibility:hidden;backface-visibility:hidden}}@media screen and (max-width:59.984375em){.md-nav--primary .md-nav__link[for=__toc]{display:flex}.md-nav--primary .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--primary .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--primary .md-nav__link[for=__toc]~.md-nav{display:flex}.md-nav__source{background-color:var(--md-primary-fg-color--dark);color:var(--md-primary-bg-color);display:block;padding:0 .2rem}}@media screen and (min-width:60em) and (max-width:76.234375em){.md-nav--integrated .md-nav__link[for=__toc]{display:flex}.md-nav--integrated .md-nav__link[for=__toc] .md-icon:after{content:""}.md-nav--integrated .md-nav__link[for=__toc]+.md-nav__link{display:none}.md-nav--integrated .md-nav__link[for=__toc]~.md-nav{display:flex}}@media screen and (min-width:60em){.md-nav{margin-bottom:-.4rem}.md-nav--secondary .md-nav__title{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);position:sticky;top:0;z-index:1}.md-nav--secondary .md-nav__title[for=__toc]{scroll-snap-align:start}.md-nav--secondary .md-nav__title .md-nav__icon{display:none}[dir=ltr] .md-nav--secondary .md-nav__list{padding-left:.6rem}[dir=rtl] .md-nav--secondary .md-nav__list{padding-right:.6rem}.md-nav--secondary .md-nav__list{padding-bottom:.4rem}[dir=ltr] .md-nav--secondary .md-nav__item>.md-nav__link{margin-right:.4rem}[dir=rtl] .md-nav--secondary .md-nav__item>.md-nav__link{margin-left:.4rem}}@media screen and (min-width:76.25em){.md-nav{margin-bottom:-.4rem;transition:max-height .25s cubic-bezier(.86,0,.07,1)}.md-nav--primary .md-nav__title{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);position:sticky;top:0;z-index:1}.md-nav--primary .md-nav__title[for=__drawer]{scroll-snap-align:start}.md-nav--primary .md-nav__title .md-nav__icon{display:none}[dir=ltr] .md-nav--primary .md-nav__list{padding-left:.6rem}[dir=rtl] .md-nav--primary .md-nav__list{padding-right:.6rem}.md-nav--primary .md-nav__list{padding-bottom:.4rem}[dir=ltr] .md-nav--primary .md-nav__item>.md-nav__link{margin-right:.4rem}[dir=rtl] .md-nav--primary .md-nav__item>.md-nav__link{margin-left:.4rem}.md-nav__toggle~.md-nav{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows .25s cubic-bezier(.86,0,.07,1),opacity .25s,visibility 0ms .25s;visibility:collapse}.md-nav__toggle~.md-nav>.md-nav__list{overflow:hidden}.md-nav__toggle.md-toggle--indeterminate~.md-nav,.md-nav__toggle:checked~.md-nav{grid-template-rows:1fr;opacity:1;transition:grid-template-rows .25s cubic-bezier(.86,0,.07,1),opacity .15s .1s,visibility 0ms;visibility:visible}.md-nav__toggle.md-toggle--indeterminate~.md-nav{transition:none}.md-nav__item--nested>.md-nav>.md-nav__title{display:none}.md-nav__item--section{display:block;margin:1.25em 0}.md-nav__item--section:last-child{margin-bottom:0}.md-nav__item--section>.md-nav__link{font-weight:700}.md-nav__item--section>.md-nav__link[for]{color:var(--md-default-fg-color--light)}.md-nav__item--section>.md-nav__link:not(.md-nav__container){pointer-events:none}.md-nav__item--section>.md-nav__link .md-icon,.md-nav__item--section>.md-nav__link>[for]{display:none}[dir=ltr] .md-nav__item--section>.md-nav{margin-left:-.6rem}[dir=rtl] .md-nav__item--section>.md-nav{margin-right:-.6rem}.md-nav__item--section>.md-nav{display:block;opacity:1;visibility:visible}.md-nav__item--section>.md-nav>.md-nav__list>.md-nav__item{padding:0}.md-nav__icon{border-radius:100%;height:.9rem;transition:background-color .25s;width:.9rem}.md-nav__icon:hover{background-color:var(--md-accent-fg-color--transparent)}.md-nav__icon:after{background-color:currentcolor;border-radius:100%;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-nav-icon--next);mask-image:var(--md-nav-icon--next);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:transform .25s;vertical-align:-.1rem;width:100%}[dir=rtl] .md-nav__icon:after{transform:rotate(180deg)}.md-nav__item--nested .md-nav__toggle:checked~.md-nav__link .md-nav__icon:after,.md-nav__item--nested .md-toggle--indeterminate~.md-nav__link .md-nav__icon:after{transform:rotate(90deg)}.md-nav--lifted>.md-nav__list>.md-nav__item,.md-nav--lifted>.md-nav__title{display:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active{display:block}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link{background:var(--md-default-bg-color);box-shadow:0 0 .4rem .4rem var(--md-default-bg-color);margin-top:0;position:sticky;top:0;z-index:1}.md-nav--lifted>.md-nav__list>.md-nav__item--active>.md-nav__link:not(.md-nav__container){pointer-events:none}.md-nav--lifted>.md-nav__list>.md-nav__item--active.md-nav__item--section{margin:0}[dir=ltr] .md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav:not(.md-nav--secondary){margin-left:-.6rem}[dir=rtl] .md-nav--lifted>.md-nav__list>.md-nav__item>.md-nav:not(.md-nav--secondary){margin-right:-.6rem}.md-nav--lifted>.md-nav__list>.md-nav__item>[for]{color:var(--md-default-fg-color--light)}.md-nav--lifted .md-nav[data-md-level="1"]{grid-template-rows:1fr;opacity:1;visibility:visible}[dir=ltr] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-left:.05rem solid var(--md-primary-fg-color)}[dir=rtl] .md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{border-right:.05rem solid var(--md-primary-fg-color)}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary{display:block;margin-bottom:1.25em;opacity:1;visibility:visible}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary>.md-nav__list{overflow:visible;padding-bottom:0}.md-nav--integrated>.md-nav__list>.md-nav__item--active .md-nav--secondary>.md-nav__title{display:none}}.md-pagination{font-size:.8rem;font-weight:700;gap:.4rem}.md-pagination,.md-pagination>*{align-items:center;display:flex;justify-content:center}.md-pagination>*{border-radius:.2rem;height:1.8rem;min-width:1.8rem;text-align:center}.md-pagination__current{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light)}.md-pagination__link{transition:color 125ms,background-color 125ms}.md-pagination__link:focus,.md-pagination__link:hover{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-pagination__link:focus svg,.md-pagination__link:hover svg{color:var(--md-accent-fg-color)}.md-pagination__link.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-pagination__link svg{fill:currentcolor;color:var(--md-default-fg-color--lighter);display:block;max-height:100%;width:1.2rem}.md-post__back{border-bottom:.05rem solid var(--md-default-fg-color--lightest);margin-bottom:1.2rem;padding-bottom:1.2rem}@media screen and (max-width:76.234375em){.md-post__back{display:none}}[dir=rtl] .md-post__back svg{transform:scaleX(-1)}.md-post__authors{display:flex;flex-direction:column;gap:.6rem;margin:0 .6rem 1.2rem}.md-post .md-post__meta a{transition:color 125ms}.md-post .md-post__meta a:focus,.md-post .md-post__meta a:hover{color:var(--md-accent-fg-color)}.md-post__title{color:var(--md-default-fg-color--light);font-weight:700}.md-post--excerpt{margin-bottom:3.2rem}.md-post--excerpt .md-post__header{align-items:center;display:flex;gap:.6rem;min-height:1.6rem}.md-post--excerpt .md-post__authors{align-items:center;display:inline-flex;flex-direction:row;gap:.2rem;margin:0;min-height:2.4rem}[dir=ltr] .md-post--excerpt .md-post__meta .md-meta__list{margin-right:.4rem}[dir=rtl] .md-post--excerpt .md-post__meta .md-meta__list{margin-left:.4rem}.md-post--excerpt .md-post__content>:first-child{--md-scroll-margin:6rem;margin-top:0}.md-post>.md-nav--secondary{margin:1em 0}.md-profile{align-items:center;display:flex;font-size:.7rem;gap:.6rem;line-height:1.4;width:100%}.md-profile__description{flex-grow:1}.md-content--post{display:flex}@media screen and (max-width:76.234375em){.md-content--post{flex-flow:column-reverse}}.md-content--post>.md-content__inner{min-width:0}@media screen and (min-width:76.25em){[dir=ltr] .md-content--post>.md-content__inner{margin-left:1.2rem}[dir=rtl] .md-content--post>.md-content__inner{margin-right:1.2rem}}@media screen and (max-width:76.234375em){.md-sidebar.md-sidebar--post{padding:0;position:static;width:100%}.md-sidebar.md-sidebar--post .md-sidebar__scrollwrap{overflow:visible}.md-sidebar.md-sidebar--post .md-sidebar__inner{padding:0}.md-sidebar.md-sidebar--post .md-post__meta{margin-left:.6rem;margin-right:.6rem}.md-sidebar.md-sidebar--post .md-nav__item{border:none;display:inline}.md-sidebar.md-sidebar--post .md-nav__list{display:inline-flex;flex-wrap:wrap;gap:.6rem;padding-bottom:.6rem;padding-top:.6rem}.md-sidebar.md-sidebar--post .md-nav__link{padding:0}.md-sidebar.md-sidebar--post .md-nav{height:auto;margin-bottom:0;position:static}}:root{--md-progress-value:0;--md-progress-delay:400ms}.md-progress{background:var(--md-primary-bg-color);height:.075rem;opacity:min(clamp(0,var(--md-progress-value),1),clamp(0,100 - var(--md-progress-value),1));position:fixed;top:0;transform:scaleX(calc(var(--md-progress-value)*1%));transform-origin:left;transition:transform .5s cubic-bezier(.19,1,.22,1),opacity .25s var(--md-progress-delay);width:100%;z-index:4}:root{--md-search-result-icon:url('data:image/svg+xml;charset=utf-8,')}.md-search{position:relative}@media screen and (min-width:60em){.md-search{padding:.2rem 0}}.no-js .md-search{display:none}.md-search__overlay{opacity:0;z-index:1}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__overlay{left:-2.2rem}[dir=rtl] .md-search__overlay{right:-2.2rem}.md-search__overlay{background-color:var(--md-default-bg-color);border-radius:1rem;height:2rem;overflow:hidden;pointer-events:none;position:absolute;top:-1rem;transform-origin:center;transition:transform .3s .1s,opacity .2s .2s;width:2rem}[data-md-toggle=search]:checked~.md-header .md-search__overlay{opacity:1;transition:transform .4s,opacity .1s}}@media screen and (min-width:60em){[dir=ltr] .md-search__overlay{left:0}[dir=rtl] .md-search__overlay{right:0}.md-search__overlay{background-color:#0000008a;cursor:pointer;height:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0}[data-md-toggle=search]:checked~.md-header .md-search__overlay{height:200vh;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@media screen and (max-width:29.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(45)}}@media screen and (min-width:30em) and (max-width:44.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(60)}}@media screen and (min-width:45em) and (max-width:59.984375em){[data-md-toggle=search]:checked~.md-header .md-search__overlay{transform:scale(75)}}.md-search__inner{-webkit-backface-visibility:hidden;backface-visibility:hidden}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__inner{left:0}[dir=rtl] .md-search__inner{right:0}.md-search__inner{height:0;opacity:0;overflow:hidden;position:fixed;top:0;transform:translateX(5%);transition:width 0ms .3s,height 0ms .3s,transform .15s cubic-bezier(.4,0,.2,1) .15s,opacity .15s .15s;width:0;z-index:2}[dir=rtl] .md-search__inner{transform:translateX(-5%)}[data-md-toggle=search]:checked~.md-header .md-search__inner{height:100%;opacity:1;transform:translateX(0);transition:width 0ms 0ms,height 0ms 0ms,transform .15s cubic-bezier(.1,.7,.1,1) .15s,opacity .15s .15s;width:100%}}@media screen and (min-width:60em){[dir=ltr] .md-search__inner{float:right}[dir=rtl] .md-search__inner{float:left}.md-search__inner{padding:.1rem 0;position:relative;transition:width .25s cubic-bezier(.1,.7,.1,1);width:11.7rem}}@media screen and (min-width:60em) and (max-width:76.234375em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:23.4rem}}@media screen and (min-width:76.25em){[data-md-toggle=search]:checked~.md-header .md-search__inner{width:34.4rem}}.md-search__form{background-color:var(--md-default-bg-color);box-shadow:0 0 .6rem #0000;height:2.4rem;position:relative;transition:color .25s,background-color .25s;z-index:2}@media screen and (min-width:60em){.md-search__form{background-color:#00000042;border-radius:.1rem;height:1.8rem}.md-search__form:hover{background-color:#ffffff1f}}[data-md-toggle=search]:checked~.md-header .md-search__form{background-color:var(--md-default-bg-color);border-radius:.1rem .1rem 0 0;box-shadow:0 0 .6rem #00000012;color:var(--md-default-fg-color)}[dir=ltr] .md-search__input{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__input{padding-left:2.2rem;padding-right:3.6rem}.md-search__input{background:#0000;font-size:.9rem;height:100%;position:relative;text-overflow:ellipsis;width:100%;z-index:2}.md-search__input::placeholder{transition:color .25s}.md-search__input::placeholder,.md-search__input~.md-search__icon{color:var(--md-default-fg-color--light)}.md-search__input::-ms-clear{display:none}@media screen and (max-width:59.984375em){.md-search__input{font-size:.9rem;height:2.4rem;width:100%}}@media screen and (min-width:60em){[dir=ltr] .md-search__input{padding-left:2.2rem}[dir=rtl] .md-search__input{padding-right:2.2rem}.md-search__input{color:inherit;font-size:.8rem}.md-search__input::placeholder{color:var(--md-primary-bg-color--light)}.md-search__input+.md-search__icon{color:var(--md-primary-bg-color)}[data-md-toggle=search]:checked~.md-header .md-search__input{text-overflow:clip}[data-md-toggle=search]:checked~.md-header .md-search__input+.md-search__icon{color:var(--md-default-fg-color--light)}[data-md-toggle=search]:checked~.md-header .md-search__input::placeholder{color:#0000}}.md-search__icon{cursor:pointer;display:inline-block;height:1.2rem;transition:color .25s,opacity .25s;width:1.2rem}.md-search__icon:hover{opacity:.7}[dir=ltr] .md-search__icon[for=__search]{left:.5rem}[dir=rtl] .md-search__icon[for=__search]{right:.5rem}.md-search__icon[for=__search]{position:absolute;top:.3rem;z-index:2}[dir=rtl] .md-search__icon[for=__search] svg{transform:scaleX(-1)}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__icon[for=__search]{left:.8rem}[dir=rtl] .md-search__icon[for=__search]{right:.8rem}.md-search__icon[for=__search]{top:.6rem}.md-search__icon[for=__search] svg:first-child{display:none}}@media screen and (min-width:60em){.md-search__icon[for=__search]{pointer-events:none}.md-search__icon[for=__search] svg:last-child{display:none}}[dir=ltr] .md-search__options{right:.5rem}[dir=rtl] .md-search__options{left:.5rem}.md-search__options{pointer-events:none;position:absolute;top:.3rem;z-index:2}@media screen and (max-width:59.984375em){[dir=ltr] .md-search__options{right:.8rem}[dir=rtl] .md-search__options{left:.8rem}.md-search__options{top:.6rem}}[dir=ltr] .md-search__options>.md-icon{margin-left:.2rem}[dir=rtl] .md-search__options>.md-icon{margin-right:.2rem}.md-search__options>.md-icon{color:var(--md-default-fg-color--light);opacity:0;transform:scale(.75);transition:transform .15s cubic-bezier(.1,.7,.1,1),opacity .15s}.md-search__options>.md-icon:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>.md-icon{opacity:1;pointer-events:auto;transform:scale(1)}[data-md-toggle=search]:checked~.md-header .md-search__input:valid~.md-search__options>.md-icon:hover{opacity:.7}[dir=ltr] .md-search__suggest{padding-left:3.6rem;padding-right:2.2rem}[dir=rtl] .md-search__suggest{padding-left:2.2rem;padding-right:3.6rem}.md-search__suggest{align-items:center;color:var(--md-default-fg-color--lighter);display:flex;font-size:.9rem;height:100%;opacity:0;position:absolute;top:0;transition:opacity 50ms;white-space:nowrap;width:100%}@media screen and (min-width:60em){[dir=ltr] .md-search__suggest{padding-left:2.2rem}[dir=rtl] .md-search__suggest{padding-right:2.2rem}.md-search__suggest{font-size:.8rem}}[data-md-toggle=search]:checked~.md-header .md-search__suggest{opacity:1;transition:opacity .3s .1s}[dir=ltr] .md-search__output{border-bottom-left-radius:.1rem}[dir=ltr] .md-search__output,[dir=rtl] .md-search__output{border-bottom-right-radius:.1rem}[dir=rtl] .md-search__output{border-bottom-left-radius:.1rem}.md-search__output{overflow:hidden;position:absolute;width:100%;z-index:1}@media screen and (max-width:59.984375em){.md-search__output{bottom:0;top:2.4rem}}@media screen and (min-width:60em){.md-search__output{opacity:0;top:1.9rem;transition:opacity .4s}[data-md-toggle=search]:checked~.md-header .md-search__output{box-shadow:var(--md-shadow-z3);opacity:1}}.md-search__scrollwrap{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);height:100%;overflow-y:auto;touch-action:pan-y}@media (-webkit-max-device-pixel-ratio:1),(max-resolution:1dppx){.md-search__scrollwrap{transform:translateZ(0)}}@media screen and (min-width:60em) and (max-width:76.234375em){.md-search__scrollwrap{width:23.4rem}}@media screen and (min-width:76.25em){.md-search__scrollwrap{width:34.4rem}}@media screen and (min-width:60em){.md-search__scrollwrap{max-height:0;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin}[data-md-toggle=search]:checked~.md-header .md-search__scrollwrap{max-height:75vh}.md-search__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-search__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-search__scrollwrap::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}}.md-search-result{color:var(--md-default-fg-color);word-break:break-word}.md-search-result__meta{background-color:var(--md-default-fg-color--lightest);color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.8rem;padding:0 .8rem;scroll-snap-align:start}@media screen and (min-width:60em){[dir=ltr] .md-search-result__meta{padding-left:2.2rem}[dir=rtl] .md-search-result__meta{padding-right:2.2rem}}.md-search-result__list{list-style:none;margin:0;padding:0;-webkit-user-select:none;user-select:none}.md-search-result__item{box-shadow:0 -.05rem var(--md-default-fg-color--lightest)}.md-search-result__item:first-child{box-shadow:none}.md-search-result__link{display:block;outline:none;scroll-snap-align:start;transition:background-color .25s}.md-search-result__link:focus,.md-search-result__link:hover{background-color:var(--md-accent-fg-color--transparent)}.md-search-result__link:last-child p:last-child{margin-bottom:.6rem}.md-search-result__more>summary{cursor:pointer;display:block;outline:none;position:sticky;scroll-snap-align:start;top:0;z-index:1}.md-search-result__more>summary::marker{display:none}.md-search-result__more>summary::-webkit-details-marker{display:none}.md-search-result__more>summary>div{color:var(--md-typeset-a-color);font-size:.64rem;padding:.75em .8rem;transition:color .25s,background-color .25s}@media screen and (min-width:60em){[dir=ltr] .md-search-result__more>summary>div{padding-left:2.2rem}[dir=rtl] .md-search-result__more>summary>div{padding-right:2.2rem}}.md-search-result__more>summary:focus>div,.md-search-result__more>summary:hover>div{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-search-result__more[open]>summary{background-color:var(--md-default-bg-color)}.md-search-result__article{overflow:hidden;padding:0 .8rem;position:relative}@media screen and (min-width:60em){[dir=ltr] .md-search-result__article{padding-left:2.2rem}[dir=rtl] .md-search-result__article{padding-right:2.2rem}}[dir=ltr] .md-search-result__icon{left:0}[dir=rtl] .md-search-result__icon{right:0}.md-search-result__icon{color:var(--md-default-fg-color--light);height:1.2rem;margin:.5rem;position:absolute;width:1.2rem}@media screen and (max-width:59.984375em){.md-search-result__icon{display:none}}.md-search-result__icon:after{background-color:currentcolor;content:"";display:inline-block;height:100%;-webkit-mask-image:var(--md-search-result-icon);mask-image:var(--md-search-result-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:100%}[dir=rtl] .md-search-result__icon:after{transform:scaleX(-1)}.md-search-result .md-typeset{color:var(--md-default-fg-color--light);font-size:.64rem;line-height:1.6}.md-search-result .md-typeset h1{color:var(--md-default-fg-color);font-size:.8rem;font-weight:400;line-height:1.4;margin:.55rem 0}.md-search-result .md-typeset h1 mark{text-decoration:none}.md-search-result .md-typeset h2{color:var(--md-default-fg-color);font-size:.64rem;font-weight:700;line-height:1.6;margin:.5em 0}.md-search-result .md-typeset h2 mark{text-decoration:none}.md-search-result__terms{color:var(--md-default-fg-color);display:block;font-size:.64rem;font-style:italic;margin:.5em 0}.md-search-result mark{background-color:initial;color:var(--md-accent-fg-color);text-decoration:underline}.md-select{position:relative;z-index:1}.md-select__inner{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);left:50%;margin-top:.2rem;max-height:0;opacity:0;position:absolute;top:calc(100% - .2rem);transform:translate3d(-50%,.3rem,0);transition:transform .25s 375ms,opacity .25s .25s,max-height 0ms .5s}.md-select:focus-within .md-select__inner,.md-select:hover .md-select__inner{max-height:10rem;opacity:1;transform:translate3d(-50%,0,0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,max-height 0ms}.md-select__inner:after{border-bottom:.2rem solid #0000;border-bottom-color:var(--md-default-bg-color);border-left:.2rem solid #0000;border-right:.2rem solid #0000;border-top:0;content:"";height:0;left:50%;margin-left:-.2rem;margin-top:-.2rem;position:absolute;top:0;width:0}.md-select__list{border-radius:.1rem;font-size:.8rem;list-style-type:none;margin:0;max-height:inherit;overflow:auto;padding:0}.md-select__item{line-height:1.8rem}[dir=ltr] .md-select__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-select__link{padding-left:1.2rem;padding-right:.6rem}.md-select__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:background-color .25s,color .25s;width:100%}.md-select__link:focus,.md-select__link:hover{color:var(--md-accent-fg-color)}.md-select__link:focus{background-color:var(--md-default-fg-color--lightest)}.md-sidebar{align-self:flex-start;flex-shrink:0;padding:1.2rem 0;position:sticky;top:2.4rem;width:12.1rem}@media print{.md-sidebar{display:none}}@media screen and (max-width:76.234375em){[dir=ltr] .md-sidebar--primary{left:-12.1rem}[dir=rtl] .md-sidebar--primary{right:-12.1rem}.md-sidebar--primary{background-color:var(--md-default-bg-color);display:block;height:100%;position:fixed;top:0;transform:translateX(0);transition:transform .25s cubic-bezier(.4,0,.2,1),box-shadow .25s;width:12.1rem;z-index:5}[data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{box-shadow:var(--md-shadow-z3);transform:translateX(12.1rem)}[dir=rtl] [data-md-toggle=drawer]:checked~.md-container .md-sidebar--primary{transform:translateX(-12.1rem)}.md-sidebar--primary .md-sidebar__scrollwrap{bottom:0;left:0;margin:0;overflow:hidden;position:absolute;right:0;scroll-snap-type:none;top:0}}@media screen and (min-width:76.25em){.md-sidebar{height:0}.no-js .md-sidebar{height:auto}.md-header--lifted~.md-container .md-sidebar{top:4.8rem}}.md-sidebar--secondary{display:none;order:2}@media screen and (min-width:60em){.md-sidebar--secondary{height:0}.no-js .md-sidebar--secondary{height:auto}.md-sidebar--secondary:not([hidden]){display:block}.md-sidebar--secondary .md-sidebar__scrollwrap{touch-action:pan-y}}.md-sidebar__scrollwrap{scrollbar-gutter:stable;-webkit-backface-visibility:hidden;backface-visibility:hidden;margin:0 .2rem;overflow-y:auto;scrollbar-color:var(--md-default-fg-color--lighter) #0000;scrollbar-width:thin}.md-sidebar__scrollwrap::-webkit-scrollbar{height:.2rem;width:.2rem}.md-sidebar__scrollwrap:focus-within,.md-sidebar__scrollwrap:hover{scrollbar-color:var(--md-accent-fg-color) #0000}.md-sidebar__scrollwrap:focus-within::-webkit-scrollbar-thumb,.md-sidebar__scrollwrap:hover::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-sidebar__scrollwrap:focus-within::-webkit-scrollbar-thumb:hover,.md-sidebar__scrollwrap:hover::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}@supports selector(::-webkit-scrollbar){.md-sidebar__scrollwrap{scrollbar-gutter:auto}[dir=ltr] .md-sidebar__inner{padding-right:calc(100% - 11.5rem)}[dir=rtl] .md-sidebar__inner{padding-left:calc(100% - 11.5rem)}}@media screen and (max-width:76.234375em){.md-overlay{background-color:#0000008a;height:0;opacity:0;position:fixed;top:0;transition:width 0ms .25s,height 0ms .25s,opacity .25s;width:0;z-index:5}[data-md-toggle=drawer]:checked~.md-overlay{height:100%;opacity:1;transition:width 0ms,height 0ms,opacity .25s;width:100%}}@keyframes facts{0%{height:0}to{height:.65rem}}@keyframes fact{0%{opacity:0;transform:translateY(100%)}50%{opacity:0}to{opacity:1;transform:translateY(0)}}:root{--md-source-forks-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-repositories-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-stars-icon:url('data:image/svg+xml;charset=utf-8,');--md-source-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-source{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:block;font-size:.65rem;line-height:1.2;outline-color:var(--md-accent-fg-color);transition:opacity .25s;white-space:nowrap}.md-source:hover{opacity:.7}.md-source__icon{display:inline-block;height:2.4rem;vertical-align:middle;width:2rem}[dir=ltr] .md-source__icon svg{margin-left:.6rem}[dir=rtl] .md-source__icon svg{margin-right:.6rem}.md-source__icon svg{margin-top:.6rem}[dir=ltr] .md-source__icon+.md-source__repository{padding-left:2rem}[dir=rtl] .md-source__icon+.md-source__repository{padding-right:2rem}[dir=ltr] .md-source__icon+.md-source__repository{margin-left:-2rem}[dir=rtl] .md-source__icon+.md-source__repository{margin-right:-2rem}[dir=ltr] .md-source__repository{margin-left:.6rem}[dir=rtl] .md-source__repository{margin-right:.6rem}.md-source__repository{display:inline-block;max-width:calc(100% - 1.2rem);overflow:hidden;text-overflow:ellipsis;vertical-align:middle}.md-source__facts{display:flex;font-size:.55rem;gap:.4rem;list-style-type:none;margin:.1rem 0 0;opacity:.75;overflow:hidden;padding:0;width:100%}.md-source__repository--active .md-source__facts{animation:facts .25s ease-in}.md-source__fact{overflow:hidden;text-overflow:ellipsis}.md-source__repository--active .md-source__fact{animation:fact .4s ease-out}[dir=ltr] .md-source__fact:before{margin-right:.1rem}[dir=rtl] .md-source__fact:before{margin-left:.1rem}.md-source__fact:before{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:text-top;width:.6rem}.md-source__fact:nth-child(1n+2){flex-shrink:0}.md-source__fact--version:before{-webkit-mask-image:var(--md-source-version-icon);mask-image:var(--md-source-version-icon)}.md-source__fact--stars:before{-webkit-mask-image:var(--md-source-stars-icon);mask-image:var(--md-source-stars-icon)}.md-source__fact--forks:before{-webkit-mask-image:var(--md-source-forks-icon);mask-image:var(--md-source-forks-icon)}.md-source__fact--repositories:before{-webkit-mask-image:var(--md-source-repositories-icon);mask-image:var(--md-source-repositories-icon)}.md-source-file{margin:1em 0}[dir=ltr] .md-source-file__fact{margin-right:.6rem}[dir=rtl] .md-source-file__fact{margin-left:.6rem}.md-source-file__fact{align-items:center;color:var(--md-default-fg-color--light);display:inline-flex;font-size:.68rem;gap:.3rem}.md-source-file__fact .md-icon{flex-shrink:0;margin-bottom:.05rem}[dir=ltr] .md-source-file__fact .md-author{float:left}[dir=rtl] .md-source-file__fact .md-author{float:right}.md-source-file__fact .md-author{margin-right:.2rem}.md-source-file__fact svg{width:.9rem}:root{--md-status:url('data:image/svg+xml;charset=utf-8,');--md-status--new:url('data:image/svg+xml;charset=utf-8,');--md-status--deprecated:url('data:image/svg+xml;charset=utf-8,');--md-status--encrypted:url('data:image/svg+xml;charset=utf-8,')}.md-status:after{background-color:var(--md-default-fg-color--light);content:"";display:inline-block;height:1.125em;-webkit-mask-image:var(--md-status);mask-image:var(--md-status);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;vertical-align:text-bottom;width:1.125em}.md-status:hover:after{background-color:currentcolor}.md-status--new:after{-webkit-mask-image:var(--md-status--new);mask-image:var(--md-status--new)}.md-status--deprecated:after{-webkit-mask-image:var(--md-status--deprecated);mask-image:var(--md-status--deprecated)}.md-status--encrypted:after{-webkit-mask-image:var(--md-status--encrypted);mask-image:var(--md-status--encrypted)}.md-tabs{background-color:var(--md-primary-fg-color);color:var(--md-primary-bg-color);display:block;line-height:1.3;overflow:auto;width:100%;z-index:3}@media print{.md-tabs{display:none}}@media screen and (max-width:76.234375em){.md-tabs{display:none}}.md-tabs[hidden]{pointer-events:none}[dir=ltr] .md-tabs__list{margin-left:.2rem}[dir=rtl] .md-tabs__list{margin-right:.2rem}.md-tabs__list{contain:content;display:flex;list-style:none;margin:0;overflow:auto;padding:0;scrollbar-width:none;white-space:nowrap}.md-tabs__list::-webkit-scrollbar{display:none}.md-tabs__item{height:2.4rem;padding-left:.6rem;padding-right:.6rem}.md-tabs__item--active .md-tabs__link{color:inherit;opacity:1}.md-tabs__link{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:flex;font-size:.7rem;margin-top:.8rem;opacity:.7;outline-color:var(--md-accent-fg-color);outline-offset:.2rem;transition:transform .4s cubic-bezier(.1,.7,.1,1),opacity .25s}.md-tabs__link:focus,.md-tabs__link:hover{color:inherit;opacity:1}[dir=ltr] .md-tabs__link svg{margin-right:.4rem}[dir=rtl] .md-tabs__link svg{margin-left:.4rem}.md-tabs__link svg{fill:currentcolor;height:1.3em}.md-tabs__item:nth-child(2) .md-tabs__link{transition-delay:20ms}.md-tabs__item:nth-child(3) .md-tabs__link{transition-delay:40ms}.md-tabs__item:nth-child(4) .md-tabs__link{transition-delay:60ms}.md-tabs__item:nth-child(5) .md-tabs__link{transition-delay:80ms}.md-tabs__item:nth-child(6) .md-tabs__link{transition-delay:.1s}.md-tabs__item:nth-child(7) .md-tabs__link{transition-delay:.12s}.md-tabs__item:nth-child(8) .md-tabs__link{transition-delay:.14s}.md-tabs__item:nth-child(9) .md-tabs__link{transition-delay:.16s}.md-tabs__item:nth-child(10) .md-tabs__link{transition-delay:.18s}.md-tabs__item:nth-child(11) .md-tabs__link{transition-delay:.2s}.md-tabs__item:nth-child(12) .md-tabs__link{transition-delay:.22s}.md-tabs__item:nth-child(13) .md-tabs__link{transition-delay:.24s}.md-tabs__item:nth-child(14) .md-tabs__link{transition-delay:.26s}.md-tabs__item:nth-child(15) .md-tabs__link{transition-delay:.28s}.md-tabs__item:nth-child(16) .md-tabs__link{transition-delay:.3s}.md-tabs[hidden] .md-tabs__link{opacity:0;transform:translateY(50%);transition:transform 0ms .1s,opacity .1s}:root{--md-tag-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .md-tags:not([hidden]){display:inline-flex;flex-wrap:wrap;gap:.5em;margin-bottom:.75em;margin-top:-.125em}.md-typeset .md-tag{align-items:center;background:var(--md-default-fg-color--lightest);border-radius:2.4rem;display:inline-flex;font-size:.64rem;font-size:min(.8em,.64rem);font-weight:700;gap:.5em;letter-spacing:normal;line-height:1.6;padding:.3125em .78125em}.md-typeset .md-tag[href]{-webkit-tap-highlight-color:transparent;color:inherit;outline:none;transition:color 125ms,background-color 125ms}.md-typeset .md-tag[href]:focus,.md-typeset .md-tag[href]:hover{background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}[id]>.md-typeset .md-tag{vertical-align:text-top}.md-typeset .md-tag-icon:before{background-color:var(--md-default-fg-color--lighter);content:"";display:inline-block;height:1.2em;-webkit-mask-image:var(--md-tag-icon);mask-image:var(--md-tag-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color 125ms;vertical-align:text-bottom;width:1.2em}.md-typeset .md-tag-icon[href]:focus:before,.md-typeset .md-tag-icon[href]:hover:before{background-color:var(--md-accent-bg-color)}@keyframes pulse{0%{transform:scale(.95)}75%{transform:scale(1)}to{transform:scale(.95)}}:root{--md-annotation-bg-icon:url('data:image/svg+xml;charset=utf-8,');--md-annotation-icon:url('data:image/svg+xml;charset=utf-8,')}.md-tooltip{-webkit-backface-visibility:hidden;backface-visibility:hidden;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);font-family:var(--md-text-font-family);left:clamp(var(--md-tooltip-0,0rem) + .8rem,var(--md-tooltip-x),100vw + var(--md-tooltip-0,0rem) + .8rem - var(--md-tooltip-width) - 2 * .8rem);max-width:calc(100vw - 1.6rem);opacity:0;position:absolute;top:var(--md-tooltip-y);transform:translateY(-.4rem);transition:transform 0ms .25s,opacity .25s,z-index .25s;width:var(--md-tooltip-width);z-index:0}.md-tooltip--active{opacity:1;transform:translateY(0);transition:transform .25s cubic-bezier(.1,.7,.1,1),opacity .25s,z-index 0ms;z-index:2}.md-tooltip--inline{font-weight:700;-webkit-user-select:none;user-select:none;width:auto}.md-tooltip--inline:not(.md-tooltip--active){transform:translateY(.2rem) scale(.9)}.md-tooltip--inline .md-tooltip__inner{font-size:.5rem;padding:.2rem .4rem}[hidden]+.md-tooltip--inline{display:none}.focus-visible>.md-tooltip,.md-tooltip:target{outline:var(--md-accent-fg-color) auto}.md-tooltip__inner{font-size:.64rem;padding:.8rem}.md-tooltip__inner.md-typeset>:first-child{margin-top:0}.md-tooltip__inner.md-typeset>:last-child{margin-bottom:0}.md-annotation{font-weight:400;outline:none;vertical-align:text-bottom;white-space:normal}[dir=rtl] .md-annotation{direction:rtl}code .md-annotation{font-family:var(--md-code-font-family);font-size:inherit}.md-annotation:not([hidden]){display:inline-block;line-height:1.25}.md-annotation__index{border-radius:.01px;cursor:pointer;display:inline-block;margin-left:.4ch;margin-right:.4ch;outline:none;overflow:hidden;position:relative;-webkit-user-select:none;user-select:none;vertical-align:text-top;z-index:0}.md-annotation .md-annotation__index{transition:z-index .25s}@media screen{.md-annotation__index{width:2.2ch}[data-md-visible]>.md-annotation__index{animation:pulse 2s infinite}.md-annotation__index:before{background:var(--md-default-bg-color);-webkit-mask-image:var(--md-annotation-bg-icon);mask-image:var(--md-annotation-bg-icon)}.md-annotation__index:after,.md-annotation__index:before{content:"";height:2.2ch;-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:-.1ch;width:2.2ch;z-index:-1}.md-annotation__index:after{background-color:var(--md-default-fg-color--lighter);-webkit-mask-image:var(--md-annotation-icon);mask-image:var(--md-annotation-icon);transform:scale(1.0001);transition:background-color .25s,transform .25s}.md-tooltip--active+.md-annotation__index:after{transform:rotate(45deg)}.md-tooltip--active+.md-annotation__index:after,:hover>.md-annotation__index:after{background-color:var(--md-accent-fg-color)}}.md-tooltip--active+.md-annotation__index{animation-play-state:paused;transition-duration:0ms;z-index:2}.md-annotation__index [data-md-annotation-id]{display:inline-block}@media print{.md-annotation__index [data-md-annotation-id]{background:var(--md-default-fg-color--lighter);border-radius:2ch;color:var(--md-default-bg-color);font-weight:700;padding:0 .6ch;white-space:nowrap}.md-annotation__index [data-md-annotation-id]:after{content:attr(data-md-annotation-id)}}.md-typeset .md-annotation-list{counter-reset:xxx;list-style:none}.md-typeset .md-annotation-list li{position:relative}[dir=ltr] .md-typeset .md-annotation-list li:before{left:-2.125em}[dir=rtl] .md-typeset .md-annotation-list li:before{right:-2.125em}.md-typeset .md-annotation-list li:before{background:var(--md-default-fg-color--lighter);border-radius:2ch;color:var(--md-default-bg-color);content:counter(xxx);counter-increment:xxx;font-size:.8875em;font-weight:700;height:2ch;line-height:1.25;min-width:2ch;padding:0 .6ch;position:absolute;text-align:center;top:.25em}:root{--md-tooltip-width:20rem;--md-tooltip-tail:0.3rem}.md-tooltip2{-webkit-backface-visibility:hidden;backface-visibility:hidden;color:var(--md-default-fg-color);font-family:var(--md-text-font-family);opacity:0;pointer-events:none;position:absolute;top:calc(var(--md-tooltip-host-y) + var(--md-tooltip-y));transform:translateY(-.4rem);transform-origin:calc(var(--md-tooltip-host-x) + var(--md-tooltip-x)) 0;transition:transform 0ms .25s,opacity .25s,z-index .25s;width:100%;z-index:0}.md-tooltip2:before{border-left:var(--md-tooltip-tail) solid #0000;border-right:var(--md-tooltip-tail) solid #0000;content:"";display:block;left:clamp(1.5 * .8rem,var(--md-tooltip-host-x) + var(--md-tooltip-x) - var(--md-tooltip-tail),100vw - 2 * var(--md-tooltip-tail) - 1.5 * .8rem);position:absolute;z-index:1}.md-tooltip2--top:before{border-top:var(--md-tooltip-tail) solid var(--md-default-bg-color);bottom:calc(var(--md-tooltip-tail)*-1 + .025rem);filter:drop-shadow(0 1px 0 hsla(0,0%,0%,.05))}.md-tooltip2--bottom:before{border-bottom:var(--md-tooltip-tail) solid var(--md-default-bg-color);filter:drop-shadow(0 -1px 0 hsla(0,0%,0%,.05));top:calc(var(--md-tooltip-tail)*-1 + .025rem)}.md-tooltip2--active{opacity:1;transform:translateY(0);transition:transform .4s cubic-bezier(0,1,.5,1),opacity .25s,z-index 0ms;z-index:2}.md-tooltip2__inner{scrollbar-gutter:stable;background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);left:clamp(.8rem,var(--md-tooltip-host-x) - .8rem,100vw - var(--md-tooltip-width) - .8rem);max-height:40vh;max-width:calc(100vw - 1.6rem);position:relative;scrollbar-width:thin}.md-tooltip2__inner::-webkit-scrollbar{height:.2rem;width:.2rem}.md-tooltip2__inner::-webkit-scrollbar-thumb{background-color:var(--md-default-fg-color--lighter)}.md-tooltip2__inner::-webkit-scrollbar-thumb:hover{background-color:var(--md-accent-fg-color)}[role=tooltip]>.md-tooltip2__inner{font-size:.5rem;font-weight:700;left:clamp(.8rem,var(--md-tooltip-host-x) + var(--md-tooltip-x) - var(--md-tooltip-width)/2,100vw - var(--md-tooltip-width) - .8rem);max-width:min(100vw - 2 * .8rem,400px);padding:.2rem .4rem;-webkit-user-select:none;user-select:none;width:-moz-fit-content;width:fit-content}.md-tooltip2__inner.md-typeset>:first-child{margin-top:0}.md-tooltip2__inner.md-typeset>:last-child{margin-bottom:0}[dir=ltr] .md-top{margin-left:50%}[dir=rtl] .md-top{margin-right:50%}.md-top{background-color:var(--md-default-bg-color);border-radius:1.6rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color--light);cursor:pointer;display:block;font-size:.7rem;outline:none;padding:.4rem .8rem;position:fixed;top:3.2rem;transform:translate(-50%);transition:color 125ms,background-color 125ms,transform 125ms cubic-bezier(.4,0,.2,1),opacity 125ms;z-index:2}@media print{.md-top{display:none}}[dir=rtl] .md-top{transform:translate(50%)}.md-top[hidden]{opacity:0;pointer-events:none;transform:translate(-50%,.2rem);transition-duration:0ms}[dir=rtl] .md-top[hidden]{transform:translate(50%,.2rem)}.md-top:focus,.md-top:hover{background-color:var(--md-accent-fg-color);color:var(--md-accent-bg-color)}.md-top svg{display:inline-block;vertical-align:-.5em}@keyframes hoverfix{0%{pointer-events:none}}:root{--md-version-icon:url('data:image/svg+xml;charset=utf-8,')}.md-version{flex-shrink:0;font-size:.8rem;height:2.4rem}[dir=ltr] .md-version__current{margin-left:1.4rem;margin-right:.4rem}[dir=rtl] .md-version__current{margin-left:.4rem;margin-right:1.4rem}.md-version__current{color:inherit;cursor:pointer;outline:none;position:relative;top:.05rem}[dir=ltr] .md-version__current:after{margin-left:.4rem}[dir=rtl] .md-version__current:after{margin-right:.4rem}.md-version__current:after{background-color:currentcolor;content:"";display:inline-block;height:.6rem;-webkit-mask-image:var(--md-version-icon);mask-image:var(--md-version-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.4rem}.md-version__alias{margin-left:.3rem;opacity:.7}.md-version__list{background-color:var(--md-default-bg-color);border-radius:.1rem;box-shadow:var(--md-shadow-z2);color:var(--md-default-fg-color);list-style-type:none;margin:.2rem .8rem;max-height:0;opacity:0;overflow:auto;padding:0;position:absolute;scroll-snap-type:y mandatory;top:.15rem;transition:max-height 0ms .5s,opacity .25s .25s;z-index:3}.md-version:focus-within .md-version__list,.md-version:hover .md-version__list{max-height:10rem;opacity:1;transition:max-height 0ms,opacity .25s}@media (hover:none),(pointer:coarse){.md-version:hover .md-version__list{animation:hoverfix .25s forwards}.md-version:focus-within .md-version__list{animation:none}}.md-version__item{line-height:1.8rem}[dir=ltr] .md-version__link{padding-left:.6rem;padding-right:1.2rem}[dir=rtl] .md-version__link{padding-left:1.2rem;padding-right:.6rem}.md-version__link{cursor:pointer;display:block;outline:none;scroll-snap-align:start;transition:color .25s,background-color .25s;white-space:nowrap;width:100%}.md-version__link:focus,.md-version__link:hover{color:var(--md-accent-fg-color)}.md-version__link:focus{background-color:var(--md-default-fg-color--lightest)}:root{--md-admonition-icon--note:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--abstract:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--info:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--tip:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--success:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--question:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--warning:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--failure:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--danger:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--bug:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--example:url('data:image/svg+xml;charset=utf-8,');--md-admonition-icon--quote:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .admonition,.md-typeset details{background-color:var(--md-admonition-bg-color);border:.075rem solid #448aff;border-radius:.2rem;box-shadow:var(--md-shadow-z1);color:var(--md-admonition-fg-color);display:flow-root;font-size:.64rem;margin:1.5625em 0;padding:0 .6rem;page-break-inside:avoid;transition:box-shadow 125ms}@media print{.md-typeset .admonition,.md-typeset details{box-shadow:none}}.md-typeset .admonition:focus-within,.md-typeset details:focus-within{box-shadow:0 0 0 .2rem #448aff1a}.md-typeset .admonition>*,.md-typeset details>*{box-sizing:border-box}.md-typeset .admonition .admonition,.md-typeset .admonition details,.md-typeset details .admonition,.md-typeset details details{margin-bottom:1em;margin-top:1em}.md-typeset .admonition .md-typeset__scrollwrap,.md-typeset details .md-typeset__scrollwrap{margin:1em -.6rem}.md-typeset .admonition .md-typeset__table,.md-typeset details .md-typeset__table{padding:0 .6rem}.md-typeset .admonition>.tabbed-set:only-child,.md-typeset details>.tabbed-set:only-child{margin-top:0}html .md-typeset .admonition>:last-child,html .md-typeset details>:last-child{margin-bottom:.6rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{padding-left:2rem;padding-right:.6rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{padding-left:.6rem;padding-right:2rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{border-left-width:.2rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-right-width:.2rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary{border-top-left-radius:.1rem}[dir=ltr] .md-typeset .admonition-title,[dir=ltr] .md-typeset summary,[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-top-right-radius:.1rem}[dir=rtl] .md-typeset .admonition-title,[dir=rtl] .md-typeset summary{border-top-left-radius:.1rem}.md-typeset .admonition-title,.md-typeset summary{background-color:#448aff1a;border:none;font-weight:700;margin:0 -.6rem;padding-bottom:.4rem;padding-top:.4rem;position:relative}html .md-typeset .admonition-title:last-child,html .md-typeset summary:last-child{margin-bottom:0}[dir=ltr] .md-typeset .admonition-title:before,[dir=ltr] .md-typeset summary:before{left:.6rem}[dir=rtl] .md-typeset .admonition-title:before,[dir=rtl] .md-typeset summary:before{right:.6rem}.md-typeset .admonition-title:before,.md-typeset summary:before{background-color:#448aff;content:"";height:1rem;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;width:1rem}.md-typeset .admonition-title code,.md-typeset summary code{box-shadow:0 0 0 .05rem var(--md-default-fg-color--lightest)}.md-typeset .admonition.note,.md-typeset details.note{border-color:#448aff}.md-typeset .admonition.note:focus-within,.md-typeset details.note:focus-within{box-shadow:0 0 0 .2rem #448aff1a}.md-typeset .note>.admonition-title,.md-typeset .note>summary{background-color:#448aff1a}.md-typeset .note>.admonition-title:before,.md-typeset .note>summary:before{background-color:#448aff;-webkit-mask-image:var(--md-admonition-icon--note);mask-image:var(--md-admonition-icon--note)}.md-typeset .note>.admonition-title:after,.md-typeset .note>summary:after{color:#448aff}.md-typeset .admonition.abstract,.md-typeset details.abstract{border-color:#00b0ff}.md-typeset .admonition.abstract:focus-within,.md-typeset details.abstract:focus-within{box-shadow:0 0 0 .2rem #00b0ff1a}.md-typeset .abstract>.admonition-title,.md-typeset .abstract>summary{background-color:#00b0ff1a}.md-typeset .abstract>.admonition-title:before,.md-typeset .abstract>summary:before{background-color:#00b0ff;-webkit-mask-image:var(--md-admonition-icon--abstract);mask-image:var(--md-admonition-icon--abstract)}.md-typeset .abstract>.admonition-title:after,.md-typeset .abstract>summary:after{color:#00b0ff}.md-typeset .admonition.info,.md-typeset details.info{border-color:#00b8d4}.md-typeset .admonition.info:focus-within,.md-typeset details.info:focus-within{box-shadow:0 0 0 .2rem #00b8d41a}.md-typeset .info>.admonition-title,.md-typeset .info>summary{background-color:#00b8d41a}.md-typeset .info>.admonition-title:before,.md-typeset .info>summary:before{background-color:#00b8d4;-webkit-mask-image:var(--md-admonition-icon--info);mask-image:var(--md-admonition-icon--info)}.md-typeset .info>.admonition-title:after,.md-typeset .info>summary:after{color:#00b8d4}.md-typeset .admonition.tip,.md-typeset details.tip{border-color:#00bfa5}.md-typeset .admonition.tip:focus-within,.md-typeset details.tip:focus-within{box-shadow:0 0 0 .2rem #00bfa51a}.md-typeset .tip>.admonition-title,.md-typeset .tip>summary{background-color:#00bfa51a}.md-typeset .tip>.admonition-title:before,.md-typeset .tip>summary:before{background-color:#00bfa5;-webkit-mask-image:var(--md-admonition-icon--tip);mask-image:var(--md-admonition-icon--tip)}.md-typeset .tip>.admonition-title:after,.md-typeset .tip>summary:after{color:#00bfa5}.md-typeset .admonition.success,.md-typeset details.success{border-color:#00c853}.md-typeset .admonition.success:focus-within,.md-typeset details.success:focus-within{box-shadow:0 0 0 .2rem #00c8531a}.md-typeset .success>.admonition-title,.md-typeset .success>summary{background-color:#00c8531a}.md-typeset .success>.admonition-title:before,.md-typeset .success>summary:before{background-color:#00c853;-webkit-mask-image:var(--md-admonition-icon--success);mask-image:var(--md-admonition-icon--success)}.md-typeset .success>.admonition-title:after,.md-typeset .success>summary:after{color:#00c853}.md-typeset .admonition.question,.md-typeset details.question{border-color:#64dd17}.md-typeset .admonition.question:focus-within,.md-typeset details.question:focus-within{box-shadow:0 0 0 .2rem #64dd171a}.md-typeset .question>.admonition-title,.md-typeset .question>summary{background-color:#64dd171a}.md-typeset .question>.admonition-title:before,.md-typeset .question>summary:before{background-color:#64dd17;-webkit-mask-image:var(--md-admonition-icon--question);mask-image:var(--md-admonition-icon--question)}.md-typeset .question>.admonition-title:after,.md-typeset .question>summary:after{color:#64dd17}.md-typeset .admonition.warning,.md-typeset details.warning{border-color:#ff9100}.md-typeset .admonition.warning:focus-within,.md-typeset details.warning:focus-within{box-shadow:0 0 0 .2rem #ff91001a}.md-typeset .warning>.admonition-title,.md-typeset .warning>summary{background-color:#ff91001a}.md-typeset .warning>.admonition-title:before,.md-typeset .warning>summary:before{background-color:#ff9100;-webkit-mask-image:var(--md-admonition-icon--warning);mask-image:var(--md-admonition-icon--warning)}.md-typeset .warning>.admonition-title:after,.md-typeset .warning>summary:after{color:#ff9100}.md-typeset .admonition.failure,.md-typeset details.failure{border-color:#ff5252}.md-typeset .admonition.failure:focus-within,.md-typeset details.failure:focus-within{box-shadow:0 0 0 .2rem #ff52521a}.md-typeset .failure>.admonition-title,.md-typeset .failure>summary{background-color:#ff52521a}.md-typeset .failure>.admonition-title:before,.md-typeset .failure>summary:before{background-color:#ff5252;-webkit-mask-image:var(--md-admonition-icon--failure);mask-image:var(--md-admonition-icon--failure)}.md-typeset .failure>.admonition-title:after,.md-typeset .failure>summary:after{color:#ff5252}.md-typeset .admonition.danger,.md-typeset details.danger{border-color:#ff1744}.md-typeset .admonition.danger:focus-within,.md-typeset details.danger:focus-within{box-shadow:0 0 0 .2rem #ff17441a}.md-typeset .danger>.admonition-title,.md-typeset .danger>summary{background-color:#ff17441a}.md-typeset .danger>.admonition-title:before,.md-typeset .danger>summary:before{background-color:#ff1744;-webkit-mask-image:var(--md-admonition-icon--danger);mask-image:var(--md-admonition-icon--danger)}.md-typeset .danger>.admonition-title:after,.md-typeset .danger>summary:after{color:#ff1744}.md-typeset .admonition.bug,.md-typeset details.bug{border-color:#f50057}.md-typeset .admonition.bug:focus-within,.md-typeset details.bug:focus-within{box-shadow:0 0 0 .2rem #f500571a}.md-typeset .bug>.admonition-title,.md-typeset .bug>summary{background-color:#f500571a}.md-typeset .bug>.admonition-title:before,.md-typeset .bug>summary:before{background-color:#f50057;-webkit-mask-image:var(--md-admonition-icon--bug);mask-image:var(--md-admonition-icon--bug)}.md-typeset .bug>.admonition-title:after,.md-typeset .bug>summary:after{color:#f50057}.md-typeset .admonition.example,.md-typeset details.example{border-color:#7c4dff}.md-typeset .admonition.example:focus-within,.md-typeset details.example:focus-within{box-shadow:0 0 0 .2rem #7c4dff1a}.md-typeset .example>.admonition-title,.md-typeset .example>summary{background-color:#7c4dff1a}.md-typeset .example>.admonition-title:before,.md-typeset .example>summary:before{background-color:#7c4dff;-webkit-mask-image:var(--md-admonition-icon--example);mask-image:var(--md-admonition-icon--example)}.md-typeset .example>.admonition-title:after,.md-typeset .example>summary:after{color:#7c4dff}.md-typeset .admonition.quote,.md-typeset details.quote{border-color:#9e9e9e}.md-typeset .admonition.quote:focus-within,.md-typeset details.quote:focus-within{box-shadow:0 0 0 .2rem #9e9e9e1a}.md-typeset .quote>.admonition-title,.md-typeset .quote>summary{background-color:#9e9e9e1a}.md-typeset .quote>.admonition-title:before,.md-typeset .quote>summary:before{background-color:#9e9e9e;-webkit-mask-image:var(--md-admonition-icon--quote);mask-image:var(--md-admonition-icon--quote)}.md-typeset .quote>.admonition-title:after,.md-typeset .quote>summary:after{color:#9e9e9e}:root{--md-footnotes-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .footnote{color:var(--md-default-fg-color--light);font-size:.64rem}[dir=ltr] .md-typeset .footnote>ol{margin-left:0}[dir=rtl] .md-typeset .footnote>ol{margin-right:0}.md-typeset .footnote>ol>li{transition:color 125ms}.md-typeset .footnote>ol>li:target{color:var(--md-default-fg-color)}.md-typeset .footnote>ol>li:focus-within .footnote-backref{opacity:1;transform:translateX(0);transition:none}.md-typeset .footnote>ol>li:hover .footnote-backref,.md-typeset .footnote>ol>li:target .footnote-backref{opacity:1;transform:translateX(0)}.md-typeset .footnote>ol>li>:first-child{margin-top:0}.md-typeset .footnote-ref{font-size:.75em;font-weight:700}html .md-typeset .footnote-ref{outline-offset:.1rem}.md-typeset [id^="fnref:"]:target>.footnote-ref{outline:auto}.md-typeset .footnote-backref{color:var(--md-typeset-a-color);display:inline-block;font-size:0;opacity:0;transform:translateX(.25rem);transition:color .25s,transform .25s .25s,opacity 125ms .25s;vertical-align:text-bottom}@media print{.md-typeset .footnote-backref{color:var(--md-typeset-a-color);opacity:1;transform:translateX(0)}}[dir=rtl] .md-typeset .footnote-backref{transform:translateX(-.25rem)}.md-typeset .footnote-backref:hover{color:var(--md-accent-fg-color)}.md-typeset .footnote-backref:before{background-color:currentcolor;content:"";display:inline-block;height:.8rem;-webkit-mask-image:var(--md-footnotes-icon);mask-image:var(--md-footnotes-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;width:.8rem}[dir=rtl] .md-typeset .footnote-backref:before svg{transform:scaleX(-1)}[dir=ltr] .md-typeset .headerlink{margin-left:.5rem}[dir=rtl] .md-typeset .headerlink{margin-right:.5rem}.md-typeset .headerlink{color:var(--md-default-fg-color--lighter);display:inline-block;opacity:0;transition:color .25s,opacity 125ms}@media print{.md-typeset .headerlink{display:none}}.md-typeset .headerlink:focus,.md-typeset :hover>.headerlink,.md-typeset :target>.headerlink{opacity:1;transition:color .25s,opacity 125ms}.md-typeset .headerlink:focus,.md-typeset .headerlink:hover,.md-typeset :target>.headerlink{color:var(--md-accent-fg-color)}.md-typeset :target{--md-scroll-margin:3.6rem;--md-scroll-offset:0rem;scroll-margin-top:calc(var(--md-scroll-margin) - var(--md-scroll-offset))}@media screen and (min-width:76.25em){.md-header--lifted~.md-container .md-typeset :target{--md-scroll-margin:6rem}}.md-typeset h1:target,.md-typeset h2:target,.md-typeset h3:target{--md-scroll-offset:0.2rem}.md-typeset h4:target{--md-scroll-offset:0.15rem}.md-typeset div.arithmatex{overflow:auto}@media screen and (max-width:44.984375em){.md-typeset div.arithmatex{margin:0 -.8rem}.md-typeset div.arithmatex>*{width:min-content}}.md-typeset div.arithmatex>*{margin-left:auto!important;margin-right:auto!important;padding:0 .8rem;touch-action:auto}.md-typeset div.arithmatex>* mjx-container{margin:0!important}.md-typeset div.arithmatex mjx-assistive-mml{height:0}.md-typeset del.critic{background-color:var(--md-typeset-del-color)}.md-typeset del.critic,.md-typeset ins.critic{-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset ins.critic{background-color:var(--md-typeset-ins-color)}.md-typeset .critic.comment{-webkit-box-decoration-break:clone;box-decoration-break:clone;color:var(--md-code-hl-comment-color)}.md-typeset .critic.comment:before{content:"/* "}.md-typeset .critic.comment:after{content:" */"}.md-typeset .critic.block{box-shadow:none;display:block;margin:1em 0;overflow:auto;padding-left:.8rem;padding-right:.8rem}.md-typeset .critic.block>:first-child{margin-top:.5em}.md-typeset .critic.block>:last-child{margin-bottom:.5em}:root{--md-details-icon:url('data:image/svg+xml;charset=utf-8,')}.md-typeset details{display:flow-root;overflow:visible;padding-top:0}.md-typeset details[open]>summary:after{transform:rotate(90deg)}.md-typeset details:not([open]){box-shadow:none;padding-bottom:0}.md-typeset details:not([open])>summary{border-radius:.1rem}[dir=ltr] .md-typeset summary{padding-right:1.8rem}[dir=rtl] .md-typeset summary{padding-left:1.8rem}[dir=ltr] .md-typeset summary{border-top-left-radius:.1rem}[dir=ltr] .md-typeset summary,[dir=rtl] .md-typeset summary{border-top-right-radius:.1rem}[dir=rtl] .md-typeset summary{border-top-left-radius:.1rem}.md-typeset summary{cursor:pointer;display:block;min-height:1rem;overflow:hidden}.md-typeset summary.focus-visible{outline-color:var(--md-accent-fg-color);outline-offset:.2rem}.md-typeset summary:not(.focus-visible){-webkit-tap-highlight-color:transparent;outline:none}[dir=ltr] .md-typeset summary:after{right:.4rem}[dir=rtl] .md-typeset summary:after{left:.4rem}.md-typeset summary:after{background-color:currentcolor;content:"";height:1rem;-webkit-mask-image:var(--md-details-icon);mask-image:var(--md-details-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.625em;transform:rotate(0deg);transition:transform .25s;width:1rem}[dir=rtl] .md-typeset summary:after{transform:rotate(180deg)}.md-typeset summary::marker{display:none}.md-typeset summary::-webkit-details-marker{display:none}.md-typeset .emojione,.md-typeset .gemoji,.md-typeset .twemoji{--md-icon-size:1.125em;display:inline-flex;height:var(--md-icon-size);vertical-align:text-top}.md-typeset .emojione svg,.md-typeset .gemoji svg,.md-typeset .twemoji svg{fill:currentcolor;max-height:100%;width:var(--md-icon-size)}.md-typeset .lg,.md-typeset .xl,.md-typeset .xxl,.md-typeset .xxxl{vertical-align:text-bottom}.md-typeset .middle{vertical-align:middle}.md-typeset .lg{--md-icon-size:1.5em}.md-typeset .xl{--md-icon-size:2.25em}.md-typeset .xxl{--md-icon-size:3em}.md-typeset .xxxl{--md-icon-size:4em}.highlight .o,.highlight .ow{color:var(--md-code-hl-operator-color)}.highlight .p{color:var(--md-code-hl-punctuation-color)}.highlight .cpf,.highlight .l,.highlight .s,.highlight .s1,.highlight .s2,.highlight .sb,.highlight .sc,.highlight .si,.highlight .ss{color:var(--md-code-hl-string-color)}.highlight .cp,.highlight .se,.highlight .sh,.highlight .sr,.highlight .sx{color:var(--md-code-hl-special-color)}.highlight .il,.highlight .m,.highlight .mb,.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:var(--md-code-hl-number-color)}.highlight .k,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr,.highlight .kt{color:var(--md-code-hl-keyword-color)}.highlight .kc,.highlight .n{color:var(--md-code-hl-name-color)}.highlight .bp,.highlight .nb,.highlight .no{color:var(--md-code-hl-constant-color)}.highlight .nc,.highlight .ne,.highlight .nf,.highlight .nn{color:var(--md-code-hl-function-color)}.highlight .nd,.highlight .ni,.highlight .nl,.highlight .nt{color:var(--md-code-hl-keyword-color)}.highlight .c,.highlight .c1,.highlight .ch,.highlight .cm,.highlight .cs,.highlight .sd{color:var(--md-code-hl-comment-color)}.highlight .na,.highlight .nv,.highlight .vc,.highlight .vg,.highlight .vi{color:var(--md-code-hl-variable-color)}.highlight .ge,.highlight .gh,.highlight .go,.highlight .gp,.highlight .gr,.highlight .gs,.highlight .gt,.highlight .gu{color:var(--md-code-hl-generic-color)}.highlight .gd,.highlight .gi{border-radius:.1rem;margin:0 -.125em;padding:0 .125em}.highlight .gd{background-color:var(--md-typeset-del-color)}.highlight .gi{background-color:var(--md-typeset-ins-color)}.highlight .hll{background-color:var(--md-code-hl-color--light);box-shadow:2px 0 0 0 var(--md-code-hl-color) inset;display:block;margin:0 -1.1764705882em;padding:0 1.1764705882em}.highlight span.filename{background-color:var(--md-code-bg-color);border-bottom:.05rem solid var(--md-default-fg-color--lightest);border-top-left-radius:.1rem;border-top-right-radius:.1rem;display:flow-root;font-size:.85em;font-weight:700;margin-top:1em;padding:.6617647059em 1.1764705882em;position:relative}.highlight span.filename+pre{margin-top:0}.highlight span.filename+pre>code{border-top-left-radius:0;border-top-right-radius:0}.highlight [data-linenos]:before{background-color:var(--md-code-bg-color);box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;color:var(--md-default-fg-color--light);content:attr(data-linenos);float:left;left:-1.1764705882em;margin-left:-1.1764705882em;margin-right:1.1764705882em;padding-left:1.1764705882em;position:sticky;-webkit-user-select:none;user-select:none;z-index:3}.highlight code a[id]{position:absolute;visibility:hidden}.highlight code[data-md-copying]{display:initial}.highlight code[data-md-copying] .hll{display:contents}.highlight code[data-md-copying] .md-annotation{display:none}.highlighttable{display:flow-root}.highlighttable tbody,.highlighttable td{display:block;padding:0}.highlighttable tr{display:flex}.highlighttable pre{margin:0}.highlighttable th.filename{flex-grow:1;padding:0;text-align:left}.highlighttable th.filename span.filename{margin-top:0}.highlighttable .linenos{background-color:var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-top-left-radius:.1rem;font-size:.85em;padding:.7720588235em 0 .7720588235em 1.1764705882em;-webkit-user-select:none;user-select:none}.highlighttable .linenodiv{box-shadow:-.05rem 0 var(--md-default-fg-color--lightest) inset;padding-right:.5882352941em}.highlighttable .linenodiv pre{color:var(--md-default-fg-color--light);text-align:right}.highlighttable .code{flex:1;min-width:0}.linenodiv a{color:inherit}.md-typeset .highlighttable{direction:ltr;margin:1em 0}.md-typeset .highlighttable>tbody>tr>.code>div>pre>code{border-bottom-left-radius:0;border-top-left-radius:0}.md-typeset .highlight+.result{border:.05rem solid var(--md-code-bg-color);border-bottom-left-radius:.1rem;border-bottom-right-radius:.1rem;border-top-width:.1rem;margin-top:-1.125em;overflow:visible;padding:0 1em}.md-typeset .highlight+.result:after{clear:both;content:"";display:block}@media screen and (max-width:44.984375em){.md-content__inner>.highlight{margin:1em -.8rem}.md-content__inner>.highlight>.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.code>div>pre>code,.md-content__inner>.highlight>.highlighttable>tbody>tr>.filename span.filename,.md-content__inner>.highlight>.highlighttable>tbody>tr>.linenos,.md-content__inner>.highlight>pre>code{border-radius:0}.md-content__inner>.highlight+.result{border-left-width:0;border-radius:0;border-right-width:0;margin-left:-.8rem;margin-right:-.8rem}}.md-typeset .keys kbd:after,.md-typeset .keys kbd:before{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;color:inherit;margin:0;position:relative}.md-typeset .keys span{color:var(--md-default-fg-color--light);padding:0 .2em}.md-typeset .keys .key-alt:before,.md-typeset .keys .key-left-alt:before,.md-typeset .keys .key-right-alt:before{content:"โŽ‡";padding-right:.4em}.md-typeset .keys .key-command:before,.md-typeset .keys .key-left-command:before,.md-typeset .keys .key-right-command:before{content:"โŒ˜";padding-right:.4em}.md-typeset .keys .key-control:before,.md-typeset .keys .key-left-control:before,.md-typeset .keys .key-right-control:before{content:"โŒƒ";padding-right:.4em}.md-typeset .keys .key-left-meta:before,.md-typeset .keys .key-meta:before,.md-typeset .keys .key-right-meta:before{content:"โ—†";padding-right:.4em}.md-typeset .keys .key-left-option:before,.md-typeset .keys .key-option:before,.md-typeset .keys .key-right-option:before{content:"โŒฅ";padding-right:.4em}.md-typeset .keys .key-left-shift:before,.md-typeset .keys .key-right-shift:before,.md-typeset .keys .key-shift:before{content:"โ‡ง";padding-right:.4em}.md-typeset .keys .key-left-super:before,.md-typeset .keys .key-right-super:before,.md-typeset .keys .key-super:before{content:"โ–";padding-right:.4em}.md-typeset .keys .key-left-windows:before,.md-typeset .keys .key-right-windows:before,.md-typeset .keys .key-windows:before{content:"โŠž";padding-right:.4em}.md-typeset .keys .key-arrow-down:before{content:"โ†“";padding-right:.4em}.md-typeset .keys .key-arrow-left:before{content:"โ†";padding-right:.4em}.md-typeset .keys .key-arrow-right:before{content:"โ†’";padding-right:.4em}.md-typeset .keys .key-arrow-up:before{content:"โ†‘";padding-right:.4em}.md-typeset .keys .key-backspace:before{content:"โŒซ";padding-right:.4em}.md-typeset .keys .key-backtab:before{content:"โ‡ค";padding-right:.4em}.md-typeset .keys .key-caps-lock:before{content:"โ‡ช";padding-right:.4em}.md-typeset .keys .key-clear:before{content:"โŒง";padding-right:.4em}.md-typeset .keys .key-context-menu:before{content:"โ˜ฐ";padding-right:.4em}.md-typeset .keys .key-delete:before{content:"โŒฆ";padding-right:.4em}.md-typeset .keys .key-eject:before{content:"โ";padding-right:.4em}.md-typeset .keys .key-end:before{content:"โค“";padding-right:.4em}.md-typeset .keys .key-escape:before{content:"โŽ‹";padding-right:.4em}.md-typeset .keys .key-home:before{content:"โค’";padding-right:.4em}.md-typeset .keys .key-insert:before{content:"โŽ€";padding-right:.4em}.md-typeset .keys .key-page-down:before{content:"โ‡Ÿ";padding-right:.4em}.md-typeset .keys .key-page-up:before{content:"โ‡ž";padding-right:.4em}.md-typeset .keys .key-print-screen:before{content:"โŽ™";padding-right:.4em}.md-typeset .keys .key-tab:after{content:"โ‡ฅ";padding-left:.4em}.md-typeset .keys .key-num-enter:after{content:"โŒค";padding-left:.4em}.md-typeset .keys .key-enter:after{content:"โŽ";padding-left:.4em}:root{--md-tabbed-icon--prev:url('data:image/svg+xml;charset=utf-8,');--md-tabbed-icon--next:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .tabbed-set{border-radius:.1rem;display:flex;flex-flow:column wrap;margin:1em 0;position:relative}.md-typeset .tabbed-set>input{height:0;opacity:0;position:absolute;width:0}.md-typeset .tabbed-set>input:target{--md-scroll-offset:0.625em}.md-typeset .tabbed-set>input.focus-visible~.tabbed-labels:before{background-color:var(--md-accent-fg-color)}.md-typeset .tabbed-labels{-ms-overflow-style:none;box-shadow:0 -.05rem var(--md-default-fg-color--lightest) inset;display:flex;max-width:100%;overflow:auto;scrollbar-width:none}@media print{.md-typeset .tabbed-labels{display:contents}}@media screen{.js .md-typeset .tabbed-labels{position:relative}.js .md-typeset .tabbed-labels:before{background:var(--md-default-fg-color);bottom:0;content:"";display:block;height:2px;left:0;position:absolute;transform:translateX(var(--md-indicator-x));transition:width 225ms,background-color .25s,transform .25s;transition-timing-function:cubic-bezier(.4,0,.2,1);width:var(--md-indicator-width)}}.md-typeset .tabbed-labels::-webkit-scrollbar{display:none}.md-typeset .tabbed-labels>label{border-bottom:.1rem solid #0000;border-radius:.1rem .1rem 0 0;color:var(--md-default-fg-color--light);cursor:pointer;flex-shrink:0;font-size:.64rem;font-weight:700;padding:.78125em 1.25em .625em;scroll-margin-inline-start:1rem;transition:background-color .25s,color .25s;white-space:nowrap;width:auto}@media print{.md-typeset .tabbed-labels>label:first-child{order:1}.md-typeset .tabbed-labels>label:nth-child(2){order:2}.md-typeset .tabbed-labels>label:nth-child(3){order:3}.md-typeset .tabbed-labels>label:nth-child(4){order:4}.md-typeset .tabbed-labels>label:nth-child(5){order:5}.md-typeset .tabbed-labels>label:nth-child(6){order:6}.md-typeset .tabbed-labels>label:nth-child(7){order:7}.md-typeset .tabbed-labels>label:nth-child(8){order:8}.md-typeset .tabbed-labels>label:nth-child(9){order:9}.md-typeset .tabbed-labels>label:nth-child(10){order:10}.md-typeset .tabbed-labels>label:nth-child(11){order:11}.md-typeset .tabbed-labels>label:nth-child(12){order:12}.md-typeset .tabbed-labels>label:nth-child(13){order:13}.md-typeset .tabbed-labels>label:nth-child(14){order:14}.md-typeset .tabbed-labels>label:nth-child(15){order:15}.md-typeset .tabbed-labels>label:nth-child(16){order:16}.md-typeset .tabbed-labels>label:nth-child(17){order:17}.md-typeset .tabbed-labels>label:nth-child(18){order:18}.md-typeset .tabbed-labels>label:nth-child(19){order:19}.md-typeset .tabbed-labels>label:nth-child(20){order:20}}.md-typeset .tabbed-labels>label:hover{color:var(--md-default-fg-color)}.md-typeset .tabbed-labels>label>[href]:first-child{color:inherit}.md-typeset .tabbed-labels--linked>label{padding:0}.md-typeset .tabbed-labels--linked>label>a{display:block;padding:.78125em 1.25em .625em}.md-typeset .tabbed-content{width:100%}@media print{.md-typeset .tabbed-content{display:contents}}.md-typeset .tabbed-block{display:none}@media print{.md-typeset .tabbed-block{display:block}.md-typeset .tabbed-block:first-child{order:1}.md-typeset .tabbed-block:nth-child(2){order:2}.md-typeset .tabbed-block:nth-child(3){order:3}.md-typeset .tabbed-block:nth-child(4){order:4}.md-typeset .tabbed-block:nth-child(5){order:5}.md-typeset .tabbed-block:nth-child(6){order:6}.md-typeset .tabbed-block:nth-child(7){order:7}.md-typeset .tabbed-block:nth-child(8){order:8}.md-typeset .tabbed-block:nth-child(9){order:9}.md-typeset .tabbed-block:nth-child(10){order:10}.md-typeset .tabbed-block:nth-child(11){order:11}.md-typeset .tabbed-block:nth-child(12){order:12}.md-typeset .tabbed-block:nth-child(13){order:13}.md-typeset .tabbed-block:nth-child(14){order:14}.md-typeset .tabbed-block:nth-child(15){order:15}.md-typeset .tabbed-block:nth-child(16){order:16}.md-typeset .tabbed-block:nth-child(17){order:17}.md-typeset .tabbed-block:nth-child(18){order:18}.md-typeset .tabbed-block:nth-child(19){order:19}.md-typeset .tabbed-block:nth-child(20){order:20}}.md-typeset .tabbed-block>.highlight:first-child>pre,.md-typeset .tabbed-block>pre:first-child{margin:0}.md-typeset .tabbed-block>.highlight:first-child>pre>code,.md-typeset .tabbed-block>pre:first-child>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child>.filename{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable{margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.filename span.filename,.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.linenos{border-top-left-radius:0;border-top-right-radius:0;margin:0}.md-typeset .tabbed-block>.highlight:first-child>.highlighttable>tbody>tr>.code>div>pre>code{border-top-left-radius:0;border-top-right-radius:0}.md-typeset .tabbed-block>.highlight:first-child+.result{margin-top:-.125em}.md-typeset .tabbed-block>.tabbed-set{margin:0}.md-typeset .tabbed-button{align-self:center;border-radius:100%;color:var(--md-default-fg-color--light);cursor:pointer;display:block;height:.9rem;margin-top:.1rem;pointer-events:auto;transition:background-color .25s;width:.9rem}.md-typeset .tabbed-button:hover{background-color:var(--md-accent-fg-color--transparent);color:var(--md-accent-fg-color)}.md-typeset .tabbed-button:after{background-color:currentcolor;content:"";display:block;height:100%;-webkit-mask-image:var(--md-tabbed-icon--prev);mask-image:var(--md-tabbed-icon--prev);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;transition:background-color .25s,transform .25s;width:100%}.md-typeset .tabbed-control{background:linear-gradient(to right,var(--md-default-bg-color) 60%,#0000);display:flex;height:1.9rem;justify-content:start;pointer-events:none;position:absolute;transition:opacity 125ms;width:1.2rem}[dir=rtl] .md-typeset .tabbed-control{transform:rotate(180deg)}.md-typeset .tabbed-control[hidden]{opacity:0}.md-typeset .tabbed-control--next{background:linear-gradient(to left,var(--md-default-bg-color) 60%,#0000);justify-content:end;right:0}.md-typeset .tabbed-control--next .tabbed-button:after{-webkit-mask-image:var(--md-tabbed-icon--next);mask-image:var(--md-tabbed-icon--next)}@media screen and (max-width:44.984375em){[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels{padding-right:.8rem}.md-content__inner>.tabbed-set .tabbed-labels{margin:0 -.8rem;max-width:100vw;scroll-padding-inline-start:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels:after{padding-left:.8rem}.md-content__inner>.tabbed-set .tabbed-labels:after{content:""}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-left:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{padding-right:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-left:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{margin-right:-.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--prev{width:2rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-right:.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{padding-left:.8rem}[dir=ltr] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-right:-.8rem}[dir=rtl] .md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{margin-left:-.8rem}.md-content__inner>.tabbed-set .tabbed-labels~.tabbed-control--next{width:2rem}}@media screen{.md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){color:var(--md-default-fg-color)}.md-typeset .no-js .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.md-typeset .no-js .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.md-typeset .no-js .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.md-typeset .no-js .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.md-typeset .no-js .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.md-typeset .no-js .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.md-typeset .no-js .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.md-typeset .no-js .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.md-typeset .no-js .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.md-typeset .no-js .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.md-typeset .no-js .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.md-typeset .no-js .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.md-typeset .no-js .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.md-typeset .no-js .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.md-typeset .no-js .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.md-typeset .no-js .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.md-typeset .no-js .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.md-typeset .no-js .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.md-typeset .no-js .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.md-typeset .no-js .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9),.no-js .md-typeset .tabbed-set>input:first-child:checked~.tabbed-labels>:first-child,.no-js .md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10),.no-js .md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11),.no-js .md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12),.no-js .md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13),.no-js .md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14),.no-js .md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15),.no-js .md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16),.no-js .md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17),.no-js .md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18),.no-js .md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19),.no-js .md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2),.no-js .md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20),.no-js .md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3),.no-js .md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4),.no-js .md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5),.no-js .md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6),.no-js .md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7),.no-js .md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8),.no-js .md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9){border-color:var(--md-default-fg-color)}}.md-typeset .tabbed-set>input:first-child.focus-visible~.tabbed-labels>:first-child,.md-typeset .tabbed-set>input:nth-child(10).focus-visible~.tabbed-labels>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11).focus-visible~.tabbed-labels>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12).focus-visible~.tabbed-labels>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13).focus-visible~.tabbed-labels>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14).focus-visible~.tabbed-labels>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15).focus-visible~.tabbed-labels>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16).focus-visible~.tabbed-labels>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17).focus-visible~.tabbed-labels>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18).focus-visible~.tabbed-labels>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19).focus-visible~.tabbed-labels>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2).focus-visible~.tabbed-labels>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20).focus-visible~.tabbed-labels>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3).focus-visible~.tabbed-labels>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4).focus-visible~.tabbed-labels>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5).focus-visible~.tabbed-labels>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6).focus-visible~.tabbed-labels>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7).focus-visible~.tabbed-labels>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8).focus-visible~.tabbed-labels>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9).focus-visible~.tabbed-labels>:nth-child(9){color:var(--md-accent-fg-color)}.md-typeset .tabbed-set>input:first-child:checked~.tabbed-content>:first-child,.md-typeset .tabbed-set>input:nth-child(10):checked~.tabbed-content>:nth-child(10),.md-typeset .tabbed-set>input:nth-child(11):checked~.tabbed-content>:nth-child(11),.md-typeset .tabbed-set>input:nth-child(12):checked~.tabbed-content>:nth-child(12),.md-typeset .tabbed-set>input:nth-child(13):checked~.tabbed-content>:nth-child(13),.md-typeset .tabbed-set>input:nth-child(14):checked~.tabbed-content>:nth-child(14),.md-typeset .tabbed-set>input:nth-child(15):checked~.tabbed-content>:nth-child(15),.md-typeset .tabbed-set>input:nth-child(16):checked~.tabbed-content>:nth-child(16),.md-typeset .tabbed-set>input:nth-child(17):checked~.tabbed-content>:nth-child(17),.md-typeset .tabbed-set>input:nth-child(18):checked~.tabbed-content>:nth-child(18),.md-typeset .tabbed-set>input:nth-child(19):checked~.tabbed-content>:nth-child(19),.md-typeset .tabbed-set>input:nth-child(2):checked~.tabbed-content>:nth-child(2),.md-typeset .tabbed-set>input:nth-child(20):checked~.tabbed-content>:nth-child(20),.md-typeset .tabbed-set>input:nth-child(3):checked~.tabbed-content>:nth-child(3),.md-typeset .tabbed-set>input:nth-child(4):checked~.tabbed-content>:nth-child(4),.md-typeset .tabbed-set>input:nth-child(5):checked~.tabbed-content>:nth-child(5),.md-typeset .tabbed-set>input:nth-child(6):checked~.tabbed-content>:nth-child(6),.md-typeset .tabbed-set>input:nth-child(7):checked~.tabbed-content>:nth-child(7),.md-typeset .tabbed-set>input:nth-child(8):checked~.tabbed-content>:nth-child(8),.md-typeset .tabbed-set>input:nth-child(9):checked~.tabbed-content>:nth-child(9){display:block}:root{--md-tasklist-icon:url('data:image/svg+xml;charset=utf-8,');--md-tasklist-icon--checked:url('data:image/svg+xml;charset=utf-8,')}.md-typeset .task-list-item{list-style-type:none;position:relative}[dir=ltr] .md-typeset .task-list-item [type=checkbox]{left:-2em}[dir=rtl] .md-typeset .task-list-item [type=checkbox]{right:-2em}.md-typeset .task-list-item [type=checkbox]{position:absolute;top:.45em}.md-typeset .task-list-control [type=checkbox]{opacity:0;z-index:-1}[dir=ltr] .md-typeset .task-list-indicator:before{left:-1.5em}[dir=rtl] .md-typeset .task-list-indicator:before{right:-1.5em}.md-typeset .task-list-indicator:before{background-color:var(--md-default-fg-color--lightest);content:"";height:1.25em;-webkit-mask-image:var(--md-tasklist-icon);mask-image:var(--md-tasklist-icon);-webkit-mask-position:center;mask-position:center;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:contain;mask-size:contain;position:absolute;top:.15em;width:1.25em}.md-typeset [type=checkbox]:checked+.task-list-indicator:before{background-color:#00e676;-webkit-mask-image:var(--md-tasklist-icon--checked);mask-image:var(--md-tasklist-icon--checked)}:root>*{--md-mermaid-font-family:var(--md-text-font-family),sans-serif;--md-mermaid-edge-color:var(--md-code-fg-color);--md-mermaid-node-bg-color:var(--md-accent-fg-color--transparent);--md-mermaid-node-fg-color:var(--md-accent-fg-color);--md-mermaid-label-bg-color:var(--md-default-bg-color);--md-mermaid-label-fg-color:var(--md-code-fg-color);--md-mermaid-sequence-actor-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-actor-fg-color:var(--md-mermaid-label-fg-color);--md-mermaid-sequence-actor-border-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-actor-line-color:var(--md-default-fg-color--lighter);--md-mermaid-sequence-actorman-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-actorman-line-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-box-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-box-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-label-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-label-fg-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-loop-bg-color:var(--md-mermaid-node-bg-color);--md-mermaid-sequence-loop-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-loop-border-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-message-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-message-line-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-note-bg-color:var(--md-mermaid-label-bg-color);--md-mermaid-sequence-note-fg-color:var(--md-mermaid-edge-color);--md-mermaid-sequence-note-border-color:var(--md-mermaid-label-fg-color);--md-mermaid-sequence-number-bg-color:var(--md-mermaid-node-fg-color);--md-mermaid-sequence-number-fg-color:var(--md-accent-bg-color)}.mermaid{line-height:normal;margin:1em 0}.md-typeset .grid{grid-gap:.4rem;display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,16rem),1fr));margin:1em 0}.md-typeset .grid.cards>ol,.md-typeset .grid.cards>ul{display:contents}.md-typeset .grid.cards>ol>li,.md-typeset .grid.cards>ul>li,.md-typeset .grid>.card{border:.05rem solid var(--md-default-fg-color--lightest);border-radius:.1rem;display:block;margin:0;padding:.8rem;transition:border .25s,box-shadow .25s}.md-typeset .grid.cards>ol>li:focus-within,.md-typeset .grid.cards>ol>li:hover,.md-typeset .grid.cards>ul>li:focus-within,.md-typeset .grid.cards>ul>li:hover,.md-typeset .grid>.card:focus-within,.md-typeset .grid>.card:hover{border-color:#0000;box-shadow:var(--md-shadow-z2)}.md-typeset .grid.cards>ol>li>hr,.md-typeset .grid.cards>ul>li>hr,.md-typeset .grid>.card>hr{margin-bottom:1em;margin-top:1em}.md-typeset .grid.cards>ol>li>:first-child,.md-typeset .grid.cards>ul>li>:first-child,.md-typeset .grid>.card>:first-child{margin-top:0}.md-typeset .grid.cards>ol>li>:last-child,.md-typeset .grid.cards>ul>li>:last-child,.md-typeset .grid>.card>:last-child{margin-bottom:0}.md-typeset .grid>*,.md-typeset .grid>.admonition,.md-typeset .grid>.highlight>*,.md-typeset .grid>.highlighttable,.md-typeset .grid>.md-typeset details,.md-typeset .grid>details,.md-typeset .grid>pre{margin-bottom:0;margin-top:0}.md-typeset .grid>.highlight>pre:only-child,.md-typeset .grid>.highlight>pre>code,.md-typeset .grid>.highlighttable,.md-typeset .grid>.highlighttable>tbody,.md-typeset .grid>.highlighttable>tbody>tr,.md-typeset .grid>.highlighttable>tbody>tr>.code,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight>pre,.md-typeset .grid>.highlighttable>tbody>tr>.code>.highlight>pre>code{height:100%}.md-typeset .grid>.tabbed-set{margin-bottom:0;margin-top:0}@media screen and (min-width:45em){[dir=ltr] .md-typeset .inline{float:left}[dir=rtl] .md-typeset .inline{float:right}[dir=ltr] .md-typeset .inline{margin-right:.8rem}[dir=rtl] .md-typeset .inline{margin-left:.8rem}.md-typeset .inline{margin-bottom:.8rem;margin-top:0;width:11.7rem}[dir=ltr] .md-typeset .inline.end{float:right}[dir=rtl] .md-typeset .inline.end{float:left}[dir=ltr] .md-typeset .inline.end{margin-left:.8rem;margin-right:0}[dir=rtl] .md-typeset .inline.end{margin-left:0;margin-right:.8rem}} \ No newline at end of file diff --git a/0.12/assets/stylesheets/main.6543a935.min.css.map b/0.12/assets/stylesheets/main.6543a935.min.css.map deleted file mode 100644 index dcac2d579..000000000 --- a/0.12/assets/stylesheets/main.6543a935.min.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["src/templates/assets/stylesheets/main/components/_meta.scss","../../../../src/templates/assets/stylesheets/main.scss","src/templates/assets/stylesheets/main/_resets.scss","src/templates/assets/stylesheets/main/_colors.scss","src/templates/assets/stylesheets/main/_icons.scss","src/templates/assets/stylesheets/main/_typeset.scss","src/templates/assets/stylesheets/utilities/_break.scss","src/templates/assets/stylesheets/main/components/_author.scss","src/templates/assets/stylesheets/main/components/_banner.scss","src/templates/assets/stylesheets/main/components/_base.scss","src/templates/assets/stylesheets/main/components/_clipboard.scss","src/templates/assets/stylesheets/main/components/_code.scss","src/templates/assets/stylesheets/main/components/_consent.scss","src/templates/assets/stylesheets/main/components/_content.scss","src/templates/assets/stylesheets/main/components/_dialog.scss","src/templates/assets/stylesheets/main/components/_feedback.scss","src/templates/assets/stylesheets/main/components/_footer.scss","src/templates/assets/stylesheets/main/components/_form.scss","src/templates/assets/stylesheets/main/components/_header.scss","node_modules/material-design-color/material-color.scss","src/templates/assets/stylesheets/main/components/_nav.scss","src/templates/assets/stylesheets/main/components/_pagination.scss","src/templates/assets/stylesheets/main/components/_post.scss","src/templates/assets/stylesheets/main/components/_progress.scss","src/templates/assets/stylesheets/main/components/_search.scss","src/templates/assets/stylesheets/main/components/_select.scss","src/templates/assets/stylesheets/main/components/_sidebar.scss","src/templates/assets/stylesheets/main/components/_source.scss","src/templates/assets/stylesheets/main/components/_status.scss","src/templates/assets/stylesheets/main/components/_tabs.scss","src/templates/assets/stylesheets/main/components/_tag.scss","src/templates/assets/stylesheets/main/components/_tooltip.scss","src/templates/assets/stylesheets/main/components/_tooltip2.scss","src/templates/assets/stylesheets/main/components/_top.scss","src/templates/assets/stylesheets/main/components/_version.scss","src/templates/assets/stylesheets/main/extensions/markdown/_admonition.scss","src/templates/assets/stylesheets/main/extensions/markdown/_footnotes.scss","src/templates/assets/stylesheets/main/extensions/markdown/_toc.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_arithmatex.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_critic.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_details.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_emoji.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_highlight.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_keys.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_tabbed.scss","src/templates/assets/stylesheets/main/extensions/pymdownx/_tasklist.scss","src/templates/assets/stylesheets/main/integrations/_mermaid.scss","src/templates/assets/stylesheets/main/modifiers/_grid.scss","src/templates/assets/stylesheets/main/modifiers/_inline.scss"],"names":[],"mappings":"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","file":"main.css"} \ No newline at end of file diff --git a/0.12/changelog.html b/0.12/changelog.html index 5899e138f..a1dcc326a 100644 --- a/0.12/changelog.html +++ b/0.12/changelog.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
- +
GitHub diff --git a/0.12/crds-api-reference/extensions.html b/0.12/crds-api-reference/extensions.html index da688e214..b63a2ef2d 100644 --- a/0.12/crds-api-reference/extensions.html +++ b/0.12/crds-api-reference/extensions.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
- +
GitHub diff --git a/0.12/crds-api-reference/integration-config.html b/0.12/crds-api-reference/integration-config.html index 595b583fe..9e7642da2 100644 --- a/0.12/crds-api-reference/integration-config.html +++ b/0.12/crds-api-reference/integration-config.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
- +
GitHub diff --git a/0.12/crds-api-reference/quota.html b/0.12/crds-api-reference/quota.html index 1c89eee74..6f31f61b8 100644 --- a/0.12/crds-api-reference/quota.html +++ b/0.12/crds-api-reference/quota.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
- +
GitHub diff --git a/0.12/crds-api-reference/template-group-instance.html b/0.12/crds-api-reference/template-group-instance.html index 234c1363d..3ce9413af 100644 --- a/0.12/crds-api-reference/template-group-instance.html +++ b/0.12/crds-api-reference/template-group-instance.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
- +
GitHub diff --git a/0.12/crds-api-reference/template-instance.html b/0.12/crds-api-reference/template-instance.html index 4195826f8..92668d512 100644 --- a/0.12/crds-api-reference/template-instance.html +++ b/0.12/crds-api-reference/template-instance.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
- +
GitHub diff --git a/0.12/crds-api-reference/template.html b/0.12/crds-api-reference/template.html index 5f71c1b19..facff2dcf 100644 --- a/0.12/crds-api-reference/template.html +++ b/0.12/crds-api-reference/template.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
- +
GitHub diff --git a/0.12/crds-api-reference/tenant.html b/0.12/crds-api-reference/tenant.html index 7a18a8ab1..efd10a4cc 100644 --- a/0.12/crds-api-reference/tenant.html +++ b/0.12/crds-api-reference/tenant.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
- +
GitHub diff --git a/0.12/eula.html b/0.12/eula.html index 28f436d67..1ef3fb227 100644 --- a/0.12/eula.html +++ b/0.12/eula.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
- +
GitHub diff --git a/0.12/explanation/console.html b/0.12/explanation/console.html index f50dbe6ed..1d44e4ffb 100644 --- a/0.12/explanation/console.html +++ b/0.12/explanation/console.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
- +
GitHub @@ -2066,6 +2066,57 @@ + + +
  • + + + Tenants/Quota + + + + + +
  • + +
  • + + + Tenants/Utilization + + +
  • @@ -2617,6 +2668,57 @@ +
  • + +
  • + + + Tenants/Quota + + + + + +
  • + +
  • + + + Tenants/Utilization + + +
  • @@ -2860,6 +2962,38 @@

    Dashboard OverviewTenants#

    Here, admins have a bird's-eye view of all tenants, with the ability to delve into each one for detailed examination and management. This section is pivotal for observing the distribution and organization of tenants within the system. More information on each tenant can be accessed by clicking the view option against each tenant name.

    tenants

    +

    Tenants/Quota#

    +

    Viewing Quota in the Tenant Console#

    +

    In this view, users can access a dedicated tab to review the quota utilization for their Tenants. Within this tab, users have the option to toggle between two different views: Aggregated Quota and Namespace Quota.

    +

    Aggregated Quota View#

    +

    tenants +This view provides users with an overview of the combined resource allocation and usage across all namespaces within their tenant. It offers a comprehensive look at the total limits and usage of resources such as CPU, memory, and other defined quotas. Users can easily monitor and manage resource distribution across their entire tenant environment from this aggregated perspective.

    +

    Namespace Quota View#

    +

    tenants +Alternatively, users can opt to view quota settings on a per-namespace basis. This view allows users to focus specifically on the resource allocation and usage within individual namespaces. By selecting this option, users gain granular insights into the resource constraints and utilization for each namespace, facilitating more targeted management and optimization of resources at the namespace level.

    +

    Tenants/Utilization#

    +

    In the Utilization tab of the tenant console, users are presented with a detailed table listing all namespaces within their tenant. This table provides essential metrics for each namespace, including CPU and memory utilization. The metrics shown include:

    +
      +
    • Cost: The cost associated with CPU and memory utilization.
    • +
    • Request Average: The average amount of CPU and memory resources requested.
    • +
    • Usage Average: The average amount of CPU and memory resources used.
    • +
    • Max: The maximum value between CPU and memory requests and used resources, calculated every 30 seconds and averaged over the selected running minutes.
    • +
    +

    Users can adjust the interval window using the provided selector to customize the time frame for the displayed data. This table allows users to quickly assess resource utilization across all namespaces, facilitating efficient resource management and cost tracking.

    +

    tenants

    +

    Upon selecting a specific namespace from the utilization table, users are directed to a detailed view that includes CPU and memory utilization graphs along with a workload table. This detailed view provides:

    +
      +
    • CPU and Memory Graphs: Visual representations of the namespace's CPU and memory usage over time, enabling users to identify trends and potential issues at a glance.
    • +
    • Workload Table: A comprehensive list of all workloads within the selected namespace, including pods, deployments, and stateful-sets. The table displays key metrics for each workload, including:
        +
      • Cost: The cost associated with the workload's CPU and memory utilization.
      • +
      • Request Average: The average amount of CPU and memory resources requested by the workload.
      • +
      • Usage Average: The average amount of CPU and memory resources used by the workload.
      • +
      • Max: The maximum value between CPU and memory requests and used resources, calculated every 30 seconds and averaged over the running minutes.
      • +
      +
    • +
    +

    This detailed view provides users with in-depth insights into resource utilization at the workload level, enabling precise monitoring and optimization of resource allocation within the selected namespace.

    +

    tenants

    Namespaces#

    Users can view all the namespaces that belong to their tenant, offering a comprehensive perspective of the accessible namespaces for tenant members. This section also provides options for detailed exploration.

    namespaces

    diff --git a/0.12/explanation/logs-metrics.html b/0.12/explanation/logs-metrics.html index da6a69940..e9aa9f1ca 100644 --- a/0.12/explanation/logs-metrics.html +++ b/0.12/explanation/logs-metrics.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/explanation/multi-tenancy-vault.html b/0.12/explanation/multi-tenancy-vault.html index 8b013d484..677dac0d5 100644 --- a/0.12/explanation/multi-tenancy-vault.html +++ b/0.12/explanation/multi-tenancy-vault.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/explanation/template.html b/0.12/explanation/template.html index b7dc4d2fc..4cea5a4eb 100644 --- a/0.12/explanation/template.html +++ b/0.12/explanation/template.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/explanation/templated-metadata-values.html b/0.12/explanation/templated-metadata-values.html index 77321f1cf..7e7e43073 100644 --- a/0.12/explanation/templated-metadata-values.html +++ b/0.12/explanation/templated-metadata-values.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/configuring-multitenant-network-isolation.html b/0.12/how-to-guides/configuring-multitenant-network-isolation.html index d026b26be..e8a308a1f 100644 --- a/0.12/how-to-guides/configuring-multitenant-network-isolation.html +++ b/0.12/how-to-guides/configuring-multitenant-network-isolation.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/copying-resources.html b/0.12/how-to-guides/copying-resources.html index 7b398c7aa..63f866e2d 100644 --- a/0.12/how-to-guides/copying-resources.html +++ b/0.12/how-to-guides/copying-resources.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/custom-metrics.html b/0.12/how-to-guides/custom-metrics.html index bd947171e..d05f51663 100644 --- a/0.12/how-to-guides/custom-metrics.html +++ b/0.12/how-to-guides/custom-metrics.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/custom-roles.html b/0.12/how-to-guides/custom-roles.html index 5e2bc8514..ddbae1352 100644 --- a/0.12/how-to-guides/custom-roles.html +++ b/0.12/how-to-guides/custom-roles.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/deploying-private-helm-charts.html b/0.12/how-to-guides/deploying-private-helm-charts.html index 2ada30e00..4abf83f71 100644 --- a/0.12/how-to-guides/deploying-private-helm-charts.html +++ b/0.12/how-to-guides/deploying-private-helm-charts.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/deploying-templates.html b/0.12/how-to-guides/deploying-templates.html index c48ed61f8..12e0d4707 100644 --- a/0.12/how-to-guides/deploying-templates.html +++ b/0.12/how-to-guides/deploying-templates.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/distributing-secrets-using-sealed-secret-template.html b/0.12/how-to-guides/distributing-secrets-using-sealed-secret-template.html index c5c3d8eb4..2c7e5f039 100644 --- a/0.12/how-to-guides/distributing-secrets-using-sealed-secret-template.html +++ b/0.12/how-to-guides/distributing-secrets-using-sealed-secret-template.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/enabling-multi-tenancy-argocd.html b/0.12/how-to-guides/enabling-multi-tenancy-argocd.html index 7b1b76c08..23d171283 100644 --- a/0.12/how-to-guides/enabling-multi-tenancy-argocd.html +++ b/0.12/how-to-guides/enabling-multi-tenancy-argocd.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/enabling-multi-tenancy-vault.html b/0.12/how-to-guides/enabling-multi-tenancy-vault.html index e82cf6f76..4fc804e28 100644 --- a/0.12/how-to-guides/enabling-multi-tenancy-vault.html +++ b/0.12/how-to-guides/enabling-multi-tenancy-vault.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/enabling-openshift-dev-workspace.html b/0.12/how-to-guides/enabling-openshift-dev-workspace.html index 014bf5d6b..a35fed948 100644 --- a/0.12/how-to-guides/enabling-openshift-dev-workspace.html +++ b/0.12/how-to-guides/enabling-openshift-dev-workspace.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/extend-default-roles.html b/0.12/how-to-guides/extend-default-roles.html index 9b8bab438..758bd7979 100644 --- a/0.12/how-to-guides/extend-default-roles.html +++ b/0.12/how-to-guides/extend-default-roles.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/graph-visualization.html b/0.12/how-to-guides/graph-visualization.html index d33f50e0c..d82d5af7e 100644 --- a/0.12/how-to-guides/graph-visualization.html +++ b/0.12/how-to-guides/graph-visualization.html @@ -9,9 +9,9 @@ - + Graph Visualization on MTO Console - Multi Tenant Operator - + @@ -727,7 +727,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/integrating-external-keycloak.html b/0.12/how-to-guides/integrating-external-keycloak.html index 470964f46..fdb573143 100644 --- a/0.12/how-to-guides/integrating-external-keycloak.html +++ b/0.12/how-to-guides/integrating-external-keycloak.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/keycloak.html b/0.12/how-to-guides/keycloak.html index 8d10c5ac3..d07d324a4 100644 --- a/0.12/how-to-guides/keycloak.html +++ b/0.12/how-to-guides/keycloak.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/mattermost.html b/0.12/how-to-guides/mattermost.html index 03fb0f5ef..c17d1f73b 100644 --- a/0.12/how-to-guides/mattermost.html +++ b/0.12/how-to-guides/mattermost.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/offboarding/uninstalling.html b/0.12/how-to-guides/offboarding/uninstalling.html index 2ac7ee993..6af57faef 100644 --- a/0.12/how-to-guides/offboarding/uninstalling.html +++ b/0.12/how-to-guides/offboarding/uninstalling.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/how-to-guides/resource-sync-by-tgi.html b/0.12/how-to-guides/resource-sync-by-tgi.html index a021e4685..4a79a6597 100644 --- a/0.12/how-to-guides/resource-sync-by-tgi.html +++ b/0.12/how-to-guides/resource-sync-by-tgi.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/images/dashboard.png b/0.12/images/dashboard.png index b860c0c19..f7d675c0d 100644 Binary files a/0.12/images/dashboard.png and b/0.12/images/dashboard.png differ diff --git a/0.12/images/namespaces.png b/0.12/images/namespaces.png index 25255d1e3..5129916b4 100644 Binary files a/0.12/images/namespaces.png and b/0.12/images/namespaces.png differ diff --git a/0.12/images/quotas.png b/0.12/images/quotas.png index 4af3e1191..e7fcfea71 100644 Binary files a/0.12/images/quotas.png and b/0.12/images/quotas.png differ diff --git a/0.12/images/showback.png b/0.12/images/showback.png index 75daa814b..df8b31a73 100644 Binary files a/0.12/images/showback.png and b/0.12/images/showback.png differ diff --git a/0.12/images/templateGroupInstances.png b/0.12/images/templateGroupInstances.png index e893af8b1..bd31a4fd1 100644 Binary files a/0.12/images/templateGroupInstances.png and b/0.12/images/templateGroupInstances.png differ diff --git a/0.12/images/templates.png b/0.12/images/templates.png index d07e98e2a..dba276ece 100644 Binary files a/0.12/images/templates.png and b/0.12/images/templates.png differ diff --git a/0.12/images/tenantQuotaAggregatedView.png b/0.12/images/tenantQuotaAggregatedView.png new file mode 100644 index 000000000..dcd817a89 Binary files /dev/null and b/0.12/images/tenantQuotaAggregatedView.png differ diff --git a/0.12/images/tenantQuotaNamespaceView.png b/0.12/images/tenantQuotaNamespaceView.png new file mode 100644 index 000000000..079daacfe Binary files /dev/null and b/0.12/images/tenantQuotaNamespaceView.png differ diff --git a/0.12/images/tenantUtilizationNamespaceStats.png b/0.12/images/tenantUtilizationNamespaceStats.png new file mode 100644 index 000000000..27d259e7b Binary files /dev/null and b/0.12/images/tenantUtilizationNamespaceStats.png differ diff --git a/0.12/images/tenantUtilizationNamespaceWorkloads.png b/0.12/images/tenantUtilizationNamespaceWorkloads.png new file mode 100644 index 000000000..289f69227 Binary files /dev/null and b/0.12/images/tenantUtilizationNamespaceWorkloads.png differ diff --git a/0.12/images/tenantUtilizationNamespaces.png b/0.12/images/tenantUtilizationNamespaces.png new file mode 100644 index 000000000..c3489b701 Binary files /dev/null and b/0.12/images/tenantUtilizationNamespaces.png differ diff --git a/0.12/images/tenants.png b/0.12/images/tenants.png index e5ea0ec07..c0e89748e 100644 Binary files a/0.12/images/tenants.png and b/0.12/images/tenants.png differ diff --git a/0.12/index.html b/0.12/index.html index 5a1e51d48..7a6cfbfb5 100644 --- a/0.12/index.html +++ b/0.12/index.html @@ -16,7 +16,7 @@ - + @@ -24,7 +24,7 @@ - + @@ -902,7 +902,7 @@
    - +
    GitHub diff --git a/0.12/installation/openshift.html b/0.12/installation/openshift.html index e64e0c7a1..815fb56b1 100644 --- a/0.12/installation/openshift.html +++ b/0.12/installation/openshift.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
    - +
    GitHub diff --git a/0.12/search/search_index.json b/0.12/search/search_index.json index 36a1b77f0..749d9bc69 100644 --- a/0.12/search/search_index.json +++ b/0.12/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"index.html","title":"Introduction","text":"

    Kubernetes is designed to support a single tenant platform; OpenShift brings some improvements with its \"Secure by default\" concepts but it is still very complex to design and orchestrate all the moving parts involved in building a secure multi-tenant platform hence making it difficult for cluster admins to host multi-tenancy in a single OpenShift cluster. If multi-tenancy is achieved by sharing a cluster, it can have many advantages, e.g. efficient resource utilization, less configuration effort and easier sharing of cluster-internal resources among different tenants. OpenShift and all managed applications provide enough primitive resources to achieve multi-tenancy, but it requires professional skills and deep knowledge of OpenShift.

    This is where Multi Tenant Operator (MTO) comes in and provides easy to manage/configure multi-tenancy. MTO provides wrappers around OpenShift resources to provide a higher level of abstraction to users. With MTO admins can configure Network and Security Policies, Resource Quotas, Limit Ranges, RBAC for every tenant, which are automatically inherited by all the namespaces and users in the tenant. Depending on the user's role, they are free to operate within their tenants in complete autonomy. MTO supports initializing new tenants using GitOps management pattern. Changes can be managed via PRs just like a typical GitOps workflow, so tenants can request changes, add new users, or remove users.

    The idea of MTO is to use namespaces as independent sandboxes, where tenant applications can run independently of each other. Cluster admins shall configure MTO's custom resources, which then become a self-service system for tenants. This minimizes the efforts of the cluster admins.

    MTO enables cluster admins to host multiple tenants in a single OpenShift Cluster, i.e.:

    • Share an OpenShift cluster with multiple tenants
    • Share managed applications with multiple tenants
    • Configure and manage tenants and their sandboxes

    MTO is also OpenShift certified

    "},{"location":"index.html#features","title":"Features","text":"

    The major features of Multi Tenant Operator (MTO) are described below.

    "},{"location":"index.html#kubernetes-multitenancy","title":"Kubernetes Multitenancy","text":"

    RBAC is one of the most complicated and error-prone parts of Kubernetes. With Multi Tenant Operator, you can rest assured that RBAC is configured with the \"least privilege\" mindset and all rules are kept up-to-date with zero manual effort.

    Multi Tenant Operator binds existing ClusterRoles to the Tenant's Namespaces used for managing access to the Namespaces and the resources they contain. You can also modify the default roles or create new roles to have full control and customize access control for your users and teams.

    Multi Tenant Operator is also able to leverage existing OpenShift groups or external groups synced from 3rd party identity management systems, for maintaining Tenant membership in your organization's current user management system.

    "},{"location":"index.html#hashicorp-vault-multitenancy","title":"HashiCorp Vault Multitenancy","text":"

    Multi Tenant Operator extends the tenants permission model to HashiCorp Vault where it can create Vault paths and greatly ease the overhead of managing RBAC in Vault. Tenant users can manage their own secrets without the concern of someone else having access to their Vault paths.

    More details on Vault Multitenancy

    "},{"location":"index.html#argocd-multitenancy","title":"ArgoCD Multitenancy","text":"

    Multi Tenant Operator is not only providing strong Multi Tenancy for the OpenShift internals but also extends the tenants permission model to ArgoCD were it can provision AppProjects and Allowed Repositories for your tenants greatly ease the overhead of managing RBAC in ArgoCD.

    More details on ArgoCD Multitenancy

    "},{"location":"index.html#resource-management","title":"Resource Management","text":"

    Multi Tenant Operator provides a mechanism for defining Resource Quotas at the tenant scope, meaning all namespaces belonging to a particular tenant share the defined quota, which is why you are able to safely enable dev teams to self serve their namespaces whilst being confident that they can only use the resources allocated based on budget and business needs.

    More details on Quota

    "},{"location":"index.html#templates-and-template-distribution","title":"Templates and Template distribution","text":"

    Multi Tenant Operator allows admins/users to define templates for namespaces, so that others can instantiate these templates to provision namespaces with batteries loaded. A template could pre-populate a namespace for certain use cases or with basic tooling required. Templates allow you to define Kubernetes manifests, Helm chart and more to be applied when the template is used to create a namespace.

    It also allows the parameterizing of these templates for flexibility and ease of use. It also provides the option to enforce the presence of templates in one tenant's or all the tenants' namespaces for configuring secure defaults.

    Common use cases for namespace templates may be:

    • Adding networking policies for multitenancy
    • Adding development tooling to a namespace
    • Deploying pre-populated databases with test data
    • Injecting new namespaces with optional credentials such as image pull secrets

    More details on Distributing Template Resources

    "},{"location":"index.html#mto-console","title":"MTO Console","text":"

    Multi Tenant Operator Console is a comprehensive user interface designed for both administrators and tenant users to manage multi-tenant environments. The MTO Console simplifies the complexity involved in handling various aspects of tenants and their related resources. It serves as a centralized monitoring hub, offering insights into the current state of tenants, namespaces, templates and quotas. It is designed to provide a quick summary/snapshot of MTO's status and facilitates easier interaction with various resources such as tenants, namespaces, templates, and quotas.

    More details on Console

    "},{"location":"index.html#showback","title":"Showback","text":"

    The showback functionality in Multi Tenant Operator (MTO) Console is a significant feature designed to enhance the management of resources and costs in multi-tenant Kubernetes environments. This feature focuses on accurately tracking the usage of resources by each tenant, and/or namespace, enabling organizations to monitor and optimize their expenditures. Furthermore, this functionality supports financial planning and budgeting by offering a clear view of operational costs associated with each tenant. This can be particularly beneficial for organizations that chargeback internal departments or external clients based on resource usage, ensuring that billing is fair and reflective of actual consumption.

    More details on Showback

    "},{"location":"index.html#hibernation","title":"Hibernation","text":"

    Multi Tenant Operator can downscale Deployments and StatefulSets in a tenant's Namespace according to a defined sleep schedule. The Deployments and StatefulSets are brought back to their required replicas according to the provided wake schedule.

    More details on Hibernation

    "},{"location":"index.html#mattermost-multitenancy","title":"Mattermost Multitenancy","text":"

    Multi Tenant Operator can manage Mattermost to create Teams for tenant users. All tenant users get a unique team and a list of predefined channels gets created. When a user is removed from the tenant, the user is also removed from the Mattermost team corresponding to tenant.

    More details on Mattermost

    "},{"location":"index.html#remote-development-namespaces","title":"Remote Development Namespaces","text":"

    Multi Tenant Operator can be configured to automatically provision a namespace in the cluster for every member of the specific tenant, that will also be preloaded with any selected templates and consume the same pool of resources from the tenants quota creating safe remote dev namespaces that teams can use as scratch namespace for rapid prototyping and development. So, every developer gets a Kubernetes-based cloud development environment that feel like working on localhost.

    More details on Sandboxes

    "},{"location":"index.html#cross-namespace-resource-distribution","title":"Cross Namespace Resource Distribution","text":"

    Multi Tenant Operator supports cloning of secrets and configmaps from one namespace to another namespace based on label selectors. It uses templates to enable users to provide reference to secrets and configmaps. It uses a template group instance to distribute those secrets and namespaces in matching namespaces, even if namespaces belong to different tenants. If template instance is used then the resources will only be mapped if namespaces belong to same tenant.

    More details on Copying Secrets and ConfigMaps

    "},{"location":"index.html#self-service","title":"Self-Service","text":"

    With Multi Tenant Operator, you can empower your users to safely provision namespaces for themselves and their teams (typically mapped to SSO groups). Team-owned namespaces and the resources inside them count towards the team's quotas rather than the user's individual limits and are automatically shared with all team members according to the access rules you configure in Multi Tenant Operator.

    Also, by leveraging Multi Tenant Operator's templating mechanism, namespaces can be provisioned and automatically pre-populated with any kind of resource or multiple resources such as network policies, docker pull secrets or even Helm charts etc

    "},{"location":"index.html#everything-as-codegitops-ready","title":"Everything as Code/GitOps Ready","text":"

    Multi Tenant Operator is designed and built to be 100% OpenShift-native and to be configured and managed the same familiar way as native OpenShift resources so is perfect for modern shops that are dedicated to GitOps as it is fully configurable using Custom Resources.

    "},{"location":"index.html#preventing-clusters-sprawl","title":"Preventing Clusters Sprawl","text":"

    As companies look to further harness the power of cloud-native, they are adopting container technologies at rapid speed, increasing the number of clusters and workloads. As the number of Kubernetes clusters grows, this is an increasing work for the Ops team. When it comes to patching security issues or upgrading clusters, teams are doing five times the amount of work.

    With Multi Tenant Operator teams can share a single cluster with multiple teams, groups of users, or departments by saving operational and management efforts. This prevents you from Kubernetes cluster sprawl.

    "},{"location":"index.html#native-experience","title":"Native Experience","text":"

    Multi Tenant Operator provides multi-tenancy with a native Kubernetes experience without introducing additional management layers, plugins, or customized binaries.

    "},{"location":"changelog.html","title":"Changelog","text":""},{"location":"changelog.html#v012x","title":"v0.12.x","text":""},{"location":"changelog.html#v01219","title":"v0.12.19","text":""},{"location":"changelog.html#fix","title":"Fix","text":"
    • Fixed a recurring issue in the Extensions controller where status changes were triggering unnecessary reconciliation loops.
    • Resolved a visibility issue where labels and annotations for sandbox namespaces were not appearing in the extension's status.
    • Addressed an issue where AppProject was being deleted upon extension CR deletion, regardless of the onDeletePurgeAppProject field value.
    • Optimized memory usage for Keycloak to address high consumption issues.
    • Resolved an issue that was causing a panic in the Extension Controller when the IntegrationConfig (IC) was not present.
    • Fixed an issue where the status was not being correctly updated when the entire Metadata block was removed from the Tenant specification.
    "},{"location":"changelog.html#v01213","title":"v0.12.13","text":""},{"location":"changelog.html#fix_1","title":"Fix","text":"
    • Resolved an issue that was preventing Vault from authenticating using the kubernetes authentication method.
    • Addressed an issue where changes to the IntegrationConfig were not updating the destination namespaces in ArgoCD's AppProject.
    • Fixed a problem where the tenant controller was preventing namespace deletion if it failed to delete external dependencies, such as Vault.
    "},{"location":"changelog.html#v0121","title":"v0.12.1","text":""},{"location":"changelog.html#fix_2","title":"Fix","text":"
    • Resolved memory consumption problems in multiple controllers by reducing the number of reconciliations.
    "},{"location":"changelog.html#v0120","title":"v0.12.0","text":""},{"location":"changelog.html#feature","title":"Feature","text":""},{"location":"changelog.html#enhanced","title":"Enhanced","text":"
    • Updated Tenant CR to v1beta3, more details in Tenant CRD
    • Added custom pricing support for Opencost, more details in Opencost
    "},{"location":"changelog.html#fix_3","title":"Fix","text":"
    • Resolved an issue in Templates that prevented the deployment of public helm charts.
    "},{"location":"changelog.html#v011x","title":"v0.11.x","text":""},{"location":"changelog.html#v0110","title":"v0.11.0","text":""},{"location":"changelog.html#feature_1","title":"Feature","text":"
    • Added support for configuring external keycloak in integrationconfig.
    • Added free tier support that allows creation of 2 tenants without license.
    "},{"location":"changelog.html#v010x","title":"v0.10.x","text":""},{"location":"changelog.html#v0106","title":"v0.10.6","text":""},{"location":"changelog.html#fix_4","title":"Fix","text":"
    • Fixed broken logs for namespace webhook where username and namespace were interchangeably used after a recent update
    "},{"location":"changelog.html#enhanced_1","title":"Enhanced","text":"
    • Made log messages more elaborative and consistent on one format for namespace webhook
    "},{"location":"changelog.html#v0105","title":"v0.10.5","text":""},{"location":"changelog.html#fix_5","title":"Fix","text":"
    • TemplateGroupInstance controller now correctly updates the TemplateGroupInstance custom resource status and the namespace count upon the deletion of a namespace.
    • Conflict between TemplateGroupInstance controller and kube-contoller-manager over mentioning of secret names in secrets or imagePullSecrets field in ServiceAccounts has been fixed by temporarily ignoring updates to or from ServiceAccounts.
    "},{"location":"changelog.html#enhanced_2","title":"Enhanced","text":"
    • Privileged service accounts mentioned in the IntegrationConfig have now access over all types of namespaces. Previously operations were denied on orphaned namespaces (the namespaces which are not part of both privileged and tenant scope). More info in Troubleshooting Guide
    • TemplateGroupInstance controller now ensures that its underlying resources are force-synced when a namespace is created or deleted.
    • Optimizations were made to ensure the reconciler in the TGI controller runs only once per watch event, reducing reconcile times.
    • The TemplateGroupInstance reconcile flow has been refined to process only the namespace for which the event was received, streamlining resource creation/deletion and improving overall efficiency.
    • Introduced new metrics to enhance the monitoring capabilities of the operator. Details at TGI Metrics Explanation
    "},{"location":"changelog.html#v0100","title":"v0.10.0","text":""},{"location":"changelog.html#feature_2","title":"Feature","text":"
    • Added support for caching for MTO Console using PostgreSQL as caching layer.
    • Added support for custom metrics with Template, Template Instance and Template Group Instance.
    • Graph visualization of Tenant and its associated resources on MTO Console.
    • Tenant and Admin level authz/authn support within MTO Console and Gateway.
    • Now in MTO console you can view cost of different Tenant resources with different date, resource type and additional filters.
    • MTO can now create a default keycloak realm, client and mto-admin user for Console.
    • Implemented Cluster Resource Quota for vanilla Kubernetes platform type.
    • Dependency of TLS secrets for MTO Webhook.
    • Added Helm Chart that would be used for installing MTO over Kubernetes.
      • And it comes with default Cert Manager manifests for certificates.
    • Support for MTO e2e.
    "},{"location":"changelog.html#fix_6","title":"Fix","text":"
    • Updated CreateMergePatch to MergeMergePatches to address issues caused by losing resourceVersion and UID when converting oldObject to newObject. This prevents problems when the object is edited by another controller.
    • In Template Resource distribution for Secret type, we now consider the source's Secret field type, preventing default creation as Opaque regardless of the source's actual type.
    • Enhanced admin permissions for tenant role in Vault to include Create, Update, Delete alongside existing Read and List privileges for the common-shared-secrets path. Viewers now have Read permission.
    "},{"location":"changelog.html#enhanced_3","title":"Enhanced","text":"
    • Started to support Kubernetes along with OpenShift as platform type.
    • Support of MTO's PostgreSQL instance as persistent storage for keycloak.
    • kube:admin is now bypassed by default to perform operations, earlier kube:admin needed to be mentioned in respective tenants to give it access over namespaces.
    "},{"location":"changelog.html#v09x","title":"v0.9.x","text":""},{"location":"changelog.html#v094","title":"v0.9.4","text":"
    • enhance: Removed Quota's default support of adding it to Tenant CR in spec.quota, if quota.tenantoperator.stakater.com/is-default: \"true\" annotation is present
    • fix: ValidatingWebhookConfiguration CRs are now owned by OLM, to handle cleanup upon operator uninstall
    • enhance: TemplateGroupInstance CRs now actively watch the resources they apply, and perform functions to make sure they are in sync with the state mentioned in their respective Templates

    More information about TemplateGroupInstance's sync at Sync Resources Deployed by TemplateGroupInstance

    "},{"location":"changelog.html#v092","title":"v0.9.2","text":"
    • fix: Values within TemplateInstances created via Tenants will no longer be duplicated on Tenant CR update
    • fix: Fixed a bug that made private namespaces become public
    "},{"location":"changelog.html#v091","title":"v0.9.1","text":"
    • fix: Allow namespace controller to reconcile without crashing, if no IC exists
    • fix: In case a group mentioned in IC doesn't exist, it won't block reconciliation or editing of MTO's manifests
    "},{"location":"changelog.html#v090","title":"v0.9.0","text":"
    • feat: Added console for tenants, templates and integration config
    • feat: Added support for custom realm name for RHSSO integration in Integration Config
    • feat: Add multiple status conditions to tenant and TGI for success and failure cases
    • feat: Show error messages with tenant and TGI status
    • fix: Stop reconciliation breaking for tenant and TGI, instead continue and show warnings
    • fix: Disable TGI/TI reconcile if mentioned template is not found.
    • fix: Disable repeated users webhook in tenant
    • enhance: Reduced API calls
    • enhance: General enhancements and improvements
    • chore: Update dependencies
    "},{"location":"changelog.html#enabling-console","title":"Enabling console","text":"
    • To enable console visit Installation, and add config to subscription for OperatorHub based installation.
    "},{"location":"changelog.html#v08x","title":"v0.8.x","text":""},{"location":"changelog.html#v083","title":"v0.8.3","text":"
    • fix: Reconcile namespaces when the group spec for tenants is changed, so new rolebindings can be created for them
    "},{"location":"changelog.html#v081","title":"v0.8.1","text":"
    • fix: Updated release pipelines
    "},{"location":"changelog.html#v080","title":"v0.8.0","text":"
    • feat: Allow custom roles for each tenant via label selector, more details in custom roles document
      • Roles mapping is a required field in MTO's IntegrationConfig. By default, it will always be filled with OpenShift's admin/edit/view roles
      • Ensure that mentioned roles exist within the cluster
      • Remove coupling with OpenShift's built-in admin/edit/view roles
    • feat: Removed coupling of ResourceSupervisor and Tenant resources
      • Added list of namespaces to hibernate within the ResourceSupervisor resource
      • Ensured that the same namespace cannot be added to two different Resource Supervisors
      • Moved ResourceSupervisor into a separate pod
      • Improved logs
    • fix: Remove bug from tenant's common and specific metadata
    • fix: Add missing field to Tenant's conversion webhook
    • fix: Fix panic in ResourceSupervisor sleep functionality due to sending on closed channel
    • chore: Update dependencies
    "},{"location":"changelog.html#v07x","title":"v0.7.x","text":""},{"location":"changelog.html#v074","title":"v0.7.4","text":"
    • maintain: Automate certification of new MTO releases on RedHat's Operator Hub
    "},{"location":"changelog.html#v073","title":"v0.7.3","text":"
    • feat: Updated Tenant CR to provide Tenant level AppProject permissions
    "},{"location":"changelog.html#v072","title":"v0.7.2","text":"
    • feat: Add support to map secrets/configmaps from one namespace to other namespaces using TI. Secrets/configmaps will only be mapped if their namespaces belong to same Tenant
    "},{"location":"changelog.html#v071","title":"v0.7.1","text":"
    • feat: Add option to keep AppProjects created by Multi Tenant Operator in case Tenant is deleted. By default, AppProjects get deleted
    • fix: Status now updates after namespaces are created
    • maintain: Changes to Helm chart's default behaviour
    "},{"location":"changelog.html#v070","title":"v0.7.0","text":"
    • feat: Add support to map secrets/configmaps from one namespace to other namespaces using TGI. Resources can be mapped from one Tenant's namespaces to some other Tenant's namespaces
    • feat: Allow creation of sandboxes that are private to the user
    • feat: Allow creation of namespaces without tenant prefix from within tenant spec
    • fix: Webhook changes will now be updated without manual intervention
    • maintain: Updated Tenant CR version from v1beta1 to v1beta2. Conversion webhook is added to facilitate transition to new version
      • see Tenant spec for updated spec
    • enhance: Better automated testing
    "},{"location":"changelog.html#v06x","title":"v0.6.x","text":""},{"location":"changelog.html#v061","title":"v0.6.1","text":"
    • fix: Update MTO service-account name in environment variable
    "},{"location":"changelog.html#v060","title":"v0.6.0","text":"
    • feat: Add support to ArgoCD AppProjects created by Tenant Controller to have their sync disabled when relevant namespaces are hibernating
    • feat: Add validation webhook for ResourceSupervisor
    • fix: Delete ResourceSupervisor when hibernation is removed from tenant CR
    • fix: CRQ and limit range not updating when quota changes
    • fix: ArgoCD AppProjects created by Tenant Controller not updating when Tenant label is added to an existing namespace
    • fix: Namespace workflow for TGI
    • fix: ResourceSupervisor deletion workflow
    • fix: Update RHSSO user filter for Vault integration
    • fix: Update regex of namespace names in tenant CRD
    • enhance: Optimize TGI and TI performance under load
    • maintain: Bump Operator-SDK and Dependencies version
    "},{"location":"changelog.html#v05x","title":"v0.5.x","text":""},{"location":"changelog.html#v054","title":"v0.5.4","text":"
    • fix: Update Helm dependency to v3.8.2
    "},{"location":"changelog.html#v053","title":"v0.5.3","text":"
    • fix: Add support for parameters in Helm chartRepository in templates
    "},{"location":"changelog.html#v052","title":"v0.5.2","text":"
    • fix: Add service name prefix for webhooks
    "},{"location":"changelog.html#v051","title":"v0.5.1","text":"
    • fix: ResourceSupervisor CR no longer requires a field for the Tenant name
    "},{"location":"changelog.html#v050","title":"v0.5.0","text":"
    • feat: Add support for tenant namespaces off-boarding. For more details check out onDelete
    • feat: Add tenant webhook for spec validation

    • fix: TemplateGroupInstance now cleans up leftover Template resources from namespaces that are no longer part of TGI namespace selector

    • fix: Fixed hibernation sync issue

    • enhance: Update tenant spec for applying common/specific namespace labels/annotations. For more details check out commonMetadata & SpecificMetadata

    • enhance: Add support for multi-pod architecture for Operator-Hub

    • chore: Remove conversion webhook for Quota and Tenant

    "},{"location":"changelog.html#v04x","title":"v0.4.x","text":""},{"location":"changelog.html#v047","title":"v0.4.7","text":"
    • feat: Add hibernation of StatefulSets and Deployments based on a timer
    • feat: New custom resource that handles hibernation
    "},{"location":"changelog.html#v046","title":"v0.4.6","text":"
    • fix: Revert v0.4.4
    "},{"location":"changelog.html#v045","title":"v0.4.5","text":"
    • feat: Add support for applying labels/annotation on specific namespaces
    "},{"location":"changelog.html#v044","title":"v0.4.4","text":"
    • fix: Update privilegedNamespaces regex
    "},{"location":"changelog.html#v043","title":"v0.4.3","text":"
    • fix: IntegrationConfig will now be synced in all pods
    "},{"location":"changelog.html#v042","title":"v0.4.2","text":"
    • feat: Added support to distribute common labels and annotations to tenant namespaces
    "},{"location":"changelog.html#v041","title":"v0.4.1","text":"
    • fix: Update dependencies to latest version
    "},{"location":"changelog.html#v040","title":"v0.4.0","text":"
    • feat: Controllers are now separated into individual pods
    "},{"location":"changelog.html#v03x","title":"v0.3.x","text":""},{"location":"changelog.html#v0333","title":"v0.3.33","text":"
    • fix: Optimize namespace reconciliation
    "},{"location":"changelog.html#v0333_1","title":"v0.3.33","text":"
    • fix: Revert v0.3.29 change till webhook network issue isn't resolved
    "},{"location":"changelog.html#v0333_2","title":"v0.3.33","text":"
    • fix: Execute webhook and controller of matching custom resource in same pod
    "},{"location":"changelog.html#v0330","title":"v0.3.30","text":"
    • feat: Namespace controller will now trigger TemplateGroupInstance when a new matching namespace is created
    "},{"location":"changelog.html#v0329","title":"v0.3.29","text":"
    • feat: Controllers are now separated into individual pods
    "},{"location":"changelog.html#v0328","title":"v0.3.28","text":"
    • fix: Enhancement of TemplateGroupInstance Namespace event listener
    "},{"location":"changelog.html#v0327","title":"v0.3.27","text":"
    • feat: TemplateGroupInstance will create resources instantly whenever a Namespace with matching labels is created
    "},{"location":"changelog.html#v0326","title":"v0.3.26","text":"
    • fix: Update reconciliation frequency of TemplateGroupInstance
    "},{"location":"changelog.html#v0325","title":"v0.3.25","text":"
    • feat: TemplateGroupInstance will now directly create template resources instead of creating TemplateInstances
    "},{"location":"changelog.html#migrating-from-pervious-version","title":"Migrating from pervious version","text":"
    • To migrate to Tenant-Operator:v0.3.25 perform the following steps
      • Downscale Tenant-Operator deployment by setting the replicas count to 0
      • Delete TemplateInstances created by TemplateGroupInstance (Naming convention of TemplateInstance created by TemplateGroupInstance is group-{Template.Name})
      • Update version of Tenant-Operator to v0.3.25 and set the replicas count to 2. After Tenant-Operator pods are up TemplateGroupInstance will create the missing resources
    "},{"location":"changelog.html#v0324","title":"v0.3.24","text":"
    • feat: Add feature to allow ArgoCD to sync specific cluster scoped custom resources, configurable via Integration Config. More details in relevant docs
    "},{"location":"changelog.html#v0323","title":"v0.3.23","text":"
    • feat: Added concurrent reconcilers for template instance controller
    "},{"location":"changelog.html#v0322","title":"v0.3.22","text":"
    • feat: Added validation webhook to prevent Tenant owners from creating RoleBindings with kind 'Group' or 'User'
    • fix: Removed redundant logs for namespace webhook
    • fix: Added missing check for users in a tenant owner's groups in namespace validation webhook
    • fix: General enhancements and improvements

    \u26a0\ufe0f Known Issues

    • caBundle field in validation webhooks is not being populated for newly added webhooks. A temporary fix is to edit the validation webhook configuration manifest without the caBundle field added in any webhook, so OpenShift can add it to all fields simultaneously
      • Edit the ValidatingWebhookConfiguration multi-tenant-operator-validating-webhook-configuration by removing all the caBundle fields of all webhooks
      • Save the manifest
      • Verify that all caBundle fields have been populated
      • Restart Tenant-Operator pods
    "},{"location":"changelog.html#v0321","title":"v0.3.21","text":"
    • feat: Added ClusterRole manager rules extension
    "},{"location":"changelog.html#v0320","title":"v0.3.20","text":"
    • fix: Fixed the recreation of underlying template resources, if resources were deleted
    "},{"location":"changelog.html#v0319","title":"v0.3.19","text":"
    • feat: Namespace webhook FailurePolicy is now set to Ignore instead of Fail
    • fix: Fixed config not being updated in namespace webhook when Integration Config is updated
    • fix: Fixed a crash that occurred in case of ArgoCD in Integration Config was not set during deletion of Tenant resource

    \u26a0\ufe0f ApiVersion v1alpha1 of Tenant and Quota custom resources has been deprecated and is scheduled to be removed in the future. The following links contain the updated structure of both resources

    • Quota v1beta1
    • Tenant v1beta1
    "},{"location":"changelog.html#v0318","title":"v0.3.18","text":"
    • fix: Add ArgoCD namespace to destination namespaces for App Projects
    "},{"location":"changelog.html#v0317","title":"v0.3.17","text":"
    • fix: Cluster administrator's permission will now have higher precedence on privileged namespaces
    "},{"location":"changelog.html#v0316","title":"v0.3.16","text":"
    • fix: Add groups mentioned in Tenant CR to ArgoCD App Project manifests' RBAC
    "},{"location":"changelog.html#v0315","title":"v0.3.15","text":"
    • feat: Add validation webhook for TemplateInstance & TemplateGroupInstance to prevent their creation in case the Template they reference does not exist
    "},{"location":"changelog.html#v0314","title":"v0.3.14","text":"
    • feat: Added Validation Webhook for Quota to prevent its deletion when a reference to it exists in any Tenant
    • feat: Added Validation Webhook for Template to prevent its deletion when a reference to it exists in any Tenant, TemplateGroupInstance or TemplateInstance
    • fix: Fixed a crash that occurred in case Integration Config was not found
    "},{"location":"changelog.html#v0313","title":"v0.3.13","text":"
    • feat: Multi Tenant Operator will now consider all namespaces to be managed if any default Integration Config is not found
    "},{"location":"changelog.html#v0312","title":"v0.3.12","text":"
    • fix: General enhancements and improvements
    "},{"location":"changelog.html#v0311","title":"v0.3.11","text":"
    • fix: Fix Quota's conversion webhook converting the wrong LimitRange field
    "},{"location":"changelog.html#v0310","title":"v0.3.10","text":"
    • fix: Fix Quota's LimitRange to its intended design by being an optional field
    "},{"location":"changelog.html#v039","title":"v0.3.9","text":"
    • feat: Add ability to prevent certain resources from syncing via ArgoCD
    "},{"location":"changelog.html#v038","title":"v0.3.8","text":"
    • feat: Add default annotation to OpenShift Projects that show description about the Project
    "},{"location":"changelog.html#v037","title":"v0.3.7","text":"
    • fix: Fix a typo in Multi Tenant Operator's Helm release
    "},{"location":"changelog.html#v036","title":"v0.3.6","text":"
    • fix: Fix ArgoCD's destinationNamespaces created by Multi Tenant Operator
    "},{"location":"changelog.html#v035","title":"v0.3.5","text":"
    • fix: Change sandbox creation from 1 for each group to 1 for each user in a group
    "},{"location":"changelog.html#v034","title":"v0.3.4","text":"
    • feat: Support creation of sandboxes for each group
    "},{"location":"changelog.html#v033","title":"v0.3.3","text":"
    • feat: Add ability to create namespaces from a list of namespace prefixes listed in the Tenant CR
    "},{"location":"changelog.html#v032","title":"v0.3.2","text":"
    • refactor: Restructure Quota CR, more details in relevant docs
    • feat: Add support for adding LimitRanges in Quota
    • feat: Add conversion webhook to convert existing v1alpha1 versions of quota to v1beta1
    "},{"location":"changelog.html#v031","title":"v0.3.1","text":"
    • feat: Add ability to create ArgoCD AppProjects per tenant, more details in relevant docs
    "},{"location":"changelog.html#v030","title":"v0.3.0","text":"
    • feat: Add support to add groups in addition to users as tenant members
    "},{"location":"changelog.html#v02x","title":"v0.2.x","text":""},{"location":"changelog.html#v0233","title":"v0.2.33","text":"
    • refactor: Restructure Tenant spec, more details in relevant docs
    • feat: Add conversion webhook to convert existing v1alpha1 versions of tenant to v1beta1
    "},{"location":"changelog.html#v0232","title":"v0.2.32","text":"
    • refactor: Restructure integration config spec, more details in relevant docs
    • feat: Allow users to input custom regex in certain fields inside of integration config, more details in relevant docs
    "},{"location":"changelog.html#v0231","title":"v0.2.31","text":"
    • feat: Add limit range for kube-RBAC-proxy
    "},{"location":"eula.html","title":"Multi Tenant Operator End User License Agreement","text":"

    Last revision date: 12 December 2022

    IMPORTANT: THIS SOFTWARE END-USER LICENSE AGREEMENT (\"EULA\") IS A LEGAL AGREEMENT (\"Agreement\") BETWEEN YOU (THE CUSTOMER, EITHER AS AN INDIVIDUAL OR, IF PURCHASED OR OTHERWISE ACQUIRED BY OR FOR AN ENTITY, AS AN ENTITY) AND Stakater AB OR ITS SUBSIDUARY (\"COMPANY\"). READ IT CAREFULLY BEFORE COMPLETING THE INSTALLATION PROCESS AND USING MULTI TENANT OPERATOR (\"SOFTWARE\"). IT PROVIDES A LICENSE TO USE THE SOFTWARE AND CONTAINS WARRANTY INFORMATION AND LIABILITY DISCLAIMERS. BY INSTALLING AND USING THE SOFTWARE, YOU ARE CONFIRMING YOUR ACCEPTANCE OF THE SOFTWARE AND AGREEING TO BECOME BOUND BY THE TERMS OF THIS AGREEMENT.

    In order to use the Software under this Agreement, you must receive a license key at the time of purchase, in accordance with the scope of use and other terms specified and as set forth in Section 1 of this Agreement.

    "},{"location":"eula.html#1-license-grant","title":"1. License Grant","text":"
    • 1.1 General Use. This Agreement grants you a non-exclusive, non-transferable, limited license to the use rights for the Software, subject to the terms and conditions in this Agreement. The Software is licensed, not sold.

    • 1.2 Electronic Delivery. All Software and license documentation shall be delivered by electronic means unless otherwise specified on the applicable invoice or at the time of purchase. Software shall be deemed delivered when it is made available for download for you by the Company (\"Delivery\").

    "},{"location":"eula.html#2-modifications","title":"2. Modifications","text":"
    • 2.1 No Modifications may be created of the original Software. \"Modification\" means:

      • (a) Any addition to or deletion from the contents of a file included in the original Software

      • (b) Any new file that contains any part of the original Software

    "},{"location":"eula.html#3-restricted-uses","title":"3. Restricted Uses","text":"
    • 3.1 You shall not (and shall not allow any third party to):

      • (a) reverse engineer the Software or attempt to reconstruct or discover any source code, underlying ideas, algorithms, file formats or programming interfaces of the Software by any means whatsoever (except and only to the extent that applicable law prohibits or restricts reverse engineering restrictions);

      • (b) distribute, sell, sub-license, rent, lease or use the Software for time sharing, hosting, service provider or like purposes, except as expressly permitted under this Agreement;

      • (c) redistribute the Software;

      • (d) remove any product identification, proprietary, copyright or other notices contained in the Software;

      • (e) modify any part of the Software, create a derivative work of any part of the Software (except as permitted in Section 4), or incorporate the Software, except to the extent expressly authorized in writing by the Company;

      • (f) publicly disseminate performance information or analysis (including, without limitation, benchmarks) from any source relating to the Software;

      • (g) utilize any equipment, device, software, or other means designed to circumvent or remove any form of Source URL or copy protection used by the Company in connection with the Software, or use the Software together with any authorization code, Source URL, serial number, or other copy protection device not supplied by the Company;

      • (h) use the Software to develop a product which is competitive with any of the Company's product offerings;

      • (i) use unauthorized Source URLs or license key(s) or distribute or publish Source URLs or license key(s), except as may be expressly permitted by the Company in writing. If your unique license is ever published, the Company reserves the right to terminate your access without notice.

    • 3.2 Under no circumstances may you use the Software as part of a product or service that provides similar functionality to the Software itself.

    "},{"location":"eula.html#4-ownership","title":"4. Ownership","text":"
    • 4.1 Notwithstanding anything to the contrary contained herein, except for the limited license rights expressly provided herein, the Company and its suppliers have and will retain all rights, title and interest (including, without limitation, all patent, copyright, trademark, trade secret and other intellectual property rights) in and to the Software and all copies, modifications and derivative works thereof (including any changes which incorporate any of your ideas, feedback or suggestions). You acknowledge that you are obtaining only a limited license right to the Software, and that irrespective of any use of the words \"purchase\", \"sale\" or like terms hereunder no ownership rights are being conveyed to you under this Agreement or otherwise.
    "},{"location":"eula.html#5-fees-and-payment","title":"5. Fees and Payment","text":"
    • 5.1 The Software license fees will be due and payable in full as set forth in the applicable invoice or at the time of purchase. You shall be responsible for all taxes, with-holdings, duties and levies arising from the order (excluding taxes based on the net income of the Company).
    "},{"location":"eula.html#6-support-maintenance-and-services","title":"6. Support, Maintenance and Services","text":"
    • 6.1 Subject to the terms and conditions of this Agreement, as set forth in your invoice, and as set forth on the Stakater support page, support and maintenance services may be included with the purchase of your license subscription.
    "},{"location":"eula.html#7-disclaimer-of-warranties","title":"7. Disclaimer of Warranties","text":"
    • 7.1 The Software is provided \"as is\", with all faults, defects and errors, and without warranty of any kind. The Company does not warrant that the Software will be free of bugs, errors, or other defects, and the Company shall have no liability of any kind for the use of or inability to use the Software, the Software content or any associated service, and you acknowledge that it is not technically practicable for the Company to do so.

    • 7.2 To the maximum extent permitted by applicable law, the Company disclaims all warranties, express, implied, arising by law or otherwise, regarding the Software, the Software content and their respective performance or suitability for your intended use, including without limitation any implied warranty of merchantability, fitness for a particular purpose.

    "},{"location":"eula.html#8-limitation-of-liability","title":"8. Limitation of Liability","text":"
    • 8.1 In no event will the Company be liable for any direct, indirect, consequential, incidental, special, exemplary, or punitive damages or liabilities whatsoever arising from or relating to the Software, the Software content or this Agreement, whether based on contract, tort (including negligence), strict liability or other theory, even if the Company has been advised of the possibility of such damages.

    • 8.2 In no event will the Company's liability exceed the Software license price as indicated in the invoice. The existence of more than one claim will not enlarge or extend this limit.

    "},{"location":"eula.html#9-remedies","title":"9. Remedies","text":"
    • 9.1 Your exclusive remedy and the Company's entire liability for breach of this Agreement shall be limited, at the Company's sole and exclusive discretion, to:

      • (a) replacement of any defective software or documentation; or

      • (b) refund of the license fee paid to the Company

    "},{"location":"eula.html#10-acknowledgements","title":"10. Acknowledgements","text":"
    • 10.1 Consent to the Use of Data. You agree that the Company and its affiliates may collect and use technical information gathered as part of the product support services. The Company may use this information solely to improve products and services and will not disclose this information in a form that personally identifies individuals or organizations.

    • 10.2 Government End Users. If the Software and related documentation are supplied to or purchased by or on behalf of a Government, then the Software is deemed to be \"commercial software\" as that term is used in the acquisition regulation system.

    "},{"location":"eula.html#11-third-party-software","title":"11. Third Party Software","text":"
    • 11.1 Examples included in Software may provide links to third party libraries or code (collectively \"Third Party Software\") to implement various functions. Third Party Software does not comprise part of the Software. In some cases, access to Third Party Software may be included along with the Software delivery as a convenience for demonstration purposes. Licensee acknowledges:

      • (1) That some part of Third Party Software may require additional licensing of copyright and patents from the owners of such, and

      • (2) That distribution of any of the Software referencing or including any portion of a Third Party Software may require appropriate licensing from such third parties

    "},{"location":"eula.html#12-miscellaneous","title":"12. Miscellaneous","text":"
    • 12.1 Entire Agreement. This Agreement sets forth our entire agreement with respect to the Software and the subject matter hereof and supersedes all prior and contemporaneous understandings and agreements whether written or oral.

    • 12.2 Amendment. The Company reserves the right, in its sole discretion, to amend this Agreement from time. Amendments are managed as described in General Provisions.

    • 12.3 Assignment. You may not assign this Agreement or any of its rights under this Agreement without the prior written consent of The Company and any attempted assignment without such consent shall be void.

    • 12.4 Export Compliance. You agree to comply with all applicable laws and regulations, including laws, regulations, orders or other restrictions on export, re-export or redistribution of software.

    • 12.5 Indemnification. You agree to defend, indemnify, and hold harmless the Company from and against any lawsuits, claims, losses, damages, fines and expenses (including attorneys' fees and costs) arising out of your use of the Software or breach of this Agreement.

    • 12.6 Attorneys' Fees and Costs. The prevailing party in any action to enforce this Agreement will be entitled to recover its attorneys' fees and costs in connection with such action.

    • 12.7 Severability. If any provision of this Agreement is held by a court of competent jurisdiction to be invalid, illegal, or unenforceable, the remainder of this Agreement will remain in full force and effect.

    • 12.8 Waiver. Failure or neglect by either party to enforce at any time any of the provisions of this license Agreement shall not be construed or deemed to be a waiver of that party's rights under this Agreement.

    • 12.9 Audit. The Company may, at its expense, appoint its own personnel or an independent third party to audit the numbers of installations of the Software in use by you. Any such audit shall be conducted upon thirty (30) days prior notice, during regular business hours and shall not unreasonably interfere with your business activities.

    • 12.10 Headings. The headings of sections and paragraphs of this Agreement are for convenience of reference only and are not intended to restrict, affect or be of any weight in the interpretation or construction of the provisions of such sections or paragraphs.

    "},{"location":"eula.html#13-contact-information","title":"13. Contact Information","text":"
    • 13.1 If you have any questions about this EULA, or if you want to contact the Company for any reason, please direct correspondence to sales@stakater.com.
    "},{"location":"troubleshooting.html","title":"Troubleshooting Guide","text":""},{"location":"troubleshooting.html#operatorhub-upgrade-error","title":"OperatorHub Upgrade Error","text":""},{"location":"troubleshooting.html#operator-is-stuck-in-upgrade-if-upgrade-approval-is-set-to-automatic","title":"Operator is stuck in upgrade if upgrade approval is set to Automatic","text":""},{"location":"troubleshooting.html#problem","title":"Problem","text":"

    If operator upgrade is set to Automatic Approval on OperatorHub, there may be scenarios where it gets blocked.

    "},{"location":"troubleshooting.html#resolution","title":"Resolution","text":"

    Information

    If upgrade approval is set to manual, and you want to skip upgrade of a specific version, then delete the InstallPlan created for that specific version. Operator Lifecycle Manager (OLM) will create the latest available InstallPlan which can be approved then.\n

    As OLM does not allow to upgrade or downgrade from a version stuck because of error, the only possible fix is to uninstall the operator from the cluster. When the operator is uninstalled it removes all of its resources i.e., ClusterRoles, ClusterRoleBindings, and Deployments etc., except Custom Resource Definitions (CRDs), so none of the Custom Resources (CRs), Tenants, Templates etc., will be removed from the cluster. If any CRD has a conversion webhook defined then that webhook should be removed before installing the stable version of the operator. This can be achieved via removing the .spec.conversion block from the CRD schema.

    As an example, if you have installed v0.8.0 of Multi Tenant Operator on your cluster, then it'll stuck in an error error validating existing CRs against new CRD's schema for \"integrationconfigs.tenantoperator.stakater.com\": error validating custom resource against new schema for IntegrationConfig multi-tenant-operator/tenant-operator-config: [].spec.tenantRoles: Required value. To resolve this issue, you'll first uninstall the MTO from the cluster. Once you uninstall the MTO, check Tenant CRD which will have a conversion block, which needs to be removed. After removing the conversion block from the Tenant CRD, install the latest available version of MTO from OperatorHub.

    "},{"location":"troubleshooting.html#permission-issues","title":"Permission Issues","text":""},{"location":"troubleshooting.html#vault-user-permissions-are-not-updated-if-the-user-is-added-to-a-tenant-and-the-user-does-not-exist-in-rhsso","title":"Vault user permissions are not updated if the user is added to a Tenant, and the user does not exist in RHSSO","text":""},{"location":"troubleshooting.html#problem_1","title":"Problem","text":"

    If a user is added to tenant resource, and the user does not exist in RHSSO, then RHSSO is not updated with the user's Vault permission.

    "},{"location":"troubleshooting.html#reproduction-steps","title":"Reproduction steps","text":"
    1. Add a new user to Tenant CR
    2. Attempt to log in to Vault with the added user
    3. Vault denies that the user exists, and signs the user up via RHSSO. User is now created on RHSSO (you may check for the user on RHSSO).
    "},{"location":"troubleshooting.html#resolution_1","title":"Resolution","text":"

    If the user does not exist in RHSSO, then MTO does not create the tenant access for Vault in RHSSO.

    The user now needs to go to Vault, and sign up using OIDC. Then the user needs to wait for MTO to reconcile the updated tenant (reconciliation period is currently 1 hour). After reconciliation, MTO will add relevant access for the user in RHSSO.

    If the user needs to be added immediately and it is not feasible to wait for next MTO reconciliation, then: add a label or annotation to the user, or restart the Tenant controller pod to force immediate reconciliation.

    "},{"location":"troubleshooting.html#pod-creation-error","title":"Pod Creation Error","text":""},{"location":"troubleshooting.html#q-errors-in-replicaset-events-about-pods-not-being-able-to-schedule-on-openshift-because-scc-annotation-is-not-found","title":"Q. Errors in ReplicaSet Events about pods not being able to schedule on OpenShift because scc annotation is not found","text":"
    unable to find annotation openshift.io/sa.scc.uid-range\n

    Answer. OpenShift recently updated its process of handling SCC, and it's now managed by annotations like openshift.io/sa.scc.uid-range on the namespaces. Absence of them wont let pods schedule. The fix for the above error is to make sure ServiceAccount system:serviceaccount:openshift-infra. regex is always mentioned in Privileged.serviceAccounts section of IntegrationConfig. This regex will allow operations from all ServiceAccounts present in openshift-infra namespace. More info at Privileged Service Accounts

    "},{"location":"troubleshooting.html#namespace-admission-webhook","title":"Namespace Admission Webhook","text":""},{"location":"troubleshooting.html#q-error-received-while-performing-create-update-or-delete-action-on-namespace","title":"Q. Error received while performing Create, Update or Delete action on Namespace","text":"
    Cannot CREATE namespace test-john without label stakater.com/tenant\n

    Answer. Error occurs when a user is trying to perform create, update, delete action on a namespace without the required stakater.com/tenant label. This label is used by the operator to see that authorized users can perform that action on the namespace. Just add the label with the tenant name so that MTO knows which tenant the namespace belongs to, and who is authorized to perform create/update/delete operations. For more details please refer to Namespace use-case.

    "},{"location":"troubleshooting.html#q-error-received-while-performing-create-update-or-delete-action-on-openshift-project","title":"Q. Error received while performing Create, Update or Delete action on OpenShift Project","text":"
    Cannot CREATE namespace testing without label stakater.com/tenant. User: system:serviceaccount:openshift-apiserver:openshift-apiserver-sa\n

    Answer. This error occurs because we don't allow Tenant members to do operations on OpenShift Project, whenever an operation is done on a project, openshift-apiserver-sa tries to do the same request onto a namespace. That's why the user sees openshift-apiserver-sa Service Account instead of its own user in the error message.

    The fix is to try the same operation on the namespace manifest instead.

    "},{"location":"troubleshooting.html#q-error-received-while-doing-kubectl-apply-f-namespaceyaml","title":"Q. Error received while doing kubectl apply -f namespace.yaml","text":"
    Error from server (Forbidden): error when retrieving current configuration of:\nResource: \"/v1, Resource=namespaces\", GroupVersionKind: \"/v1, Kind=Namespace\"\nName: \"ns1\", Namespace: \"\"\nfrom server for: \"namespace.yaml\": namespaces \"ns1\" is forbidden: User \"muneeb\" cannot get resource \"namespaces\" in API group \"\" in the namespace \"ns1\"\n

    Answer. Tenant members will not be able to use kubectl apply because apply first gets all the instances of that resource, in this case namespaces, and then does the required operation on the selected resource. To maintain tenancy, tenant members do not the access to get or list all the namespaces.

    The fix is to create namespaces with kubectl create instead.

    "},{"location":"troubleshooting.html#mto-argocd-integration","title":"MTO - ArgoCD Integration","text":""},{"location":"troubleshooting.html#q-how-do-i-deploy-cluster-scoped-resource-via-the-argocd-integration","title":"Q. How do I deploy cluster-scoped resource via the ArgoCD integration?","text":"

    Answer. Multi-Tenant Operator's ArgoCD Integration allows configuration of which cluster-scoped resources can be deployed, both globally and on a per-tenant basis. For a global allow-list that applies to all tenants, you can add both resource group and kind to the IntegrationConfig's spec.integrations.argocd.clusterResourceWhitelist field. Alternatively, you can set this up on a tenant level by configuring the same details within a Tenant's spec.integrations.argocd.appProject.clusterResourceWhitelist field. For more details, check out the ArgoCD integration use cases

    "},{"location":"troubleshooting.html#q-invalidspecerror-application-repo-repo-is-not-permitted-in-project-project","title":"Q. InvalidSpecError: application repo \\<repo> is not permitted in project \\<project>","text":"

    Answer. The above error can occur if the ArgoCD Application is syncing from a source that is not allowed the referenced AppProject. To solve this, verify that you have referred to the correct project in the given ArgoCD Application, and that the repoURL used for the Application's source is valid. If the error still appears, you can add the URL to the relevant Tenant's spec.integrations.argocd.sourceRepos array.

    "},{"location":"troubleshooting.html#mto-opencost","title":"MTO - OpenCost","text":""},{"location":"troubleshooting.html#q-why-are-there-mto-showback-pods-failing-in-my-cluster","title":"Q. Why are there mto-showback-* pods failing in my cluster?","text":"

    Answer. The mto-showback-* pods are used to calculate the cost of the resources used by each tenant. These pods are created by the Multi-Tenant Operator and are scheduled to run every 10 minutes. If the pods are failing, it is likely that the operator's necessary to calculate cost are not present in the cluster. To solve this, you can navigate to Operators -> Installed Operators in the OpenShift console and check if the MTO-OpenCost and MTO-Prometheus operators are installed. If they are in a pending state, you can manually approve them to install them in the cluster.

    "},{"location":"crds-api-reference/extensions.html","title":"Extensions","text":"

    Extensions in MTO enhance its functionality by allowing integration with external services. Currently, MTO supports integration with ArgoCD, enabling you to synchronize your repositories and configure AppProjects directly through MTO. Future updates will include support for additional integrations.

    "},{"location":"crds-api-reference/extensions.html#configuring-argocd-integration","title":"Configuring ArgoCD Integration","text":"

    Let us take a look at how you can create an Extension CR and integrate ArgoCD with MTO.

    Before you create an Extension CR, you need to modify the Integration Config resource and add the ArgoCD configuration.

      integrations:\n    argocd:\n      clusterResourceWhitelist:\n        - group: tronador.stakater.com\n          kind: EnvironmentProvisioner\n      namespaceResourceBlacklist:\n        - group: ''\n          kind: ResourceQuota\n      namespace: openshift-operators\n

    The above configuration will allow the EnvironmentProvisioner CRD and blacklist the ResourceQuota resource. Also note that the namespace field is mandatory and should be set to the namespace where the ArgoCD is deployed.

    Every Extension CR is associated with a specific Tenant. Here's an example of an Extension CR that is associated with a Tenant named tenant-sample:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Extensions\nmetadata:\n  name: extensions-sample\nspec:\n  tenantName: tenant-sample\n  argoCD:\n    onDeletePurgeAppProject: true\n    appProject:\n      sourceRepos:\n        - \"github.com/stakater/repo\"\n      clusterResourceWhitelist:\n        - group: \"\"\n          kind: \"Pod\"\n      namespaceResourceBlacklist:\n        - group: \"v1\"\n          kind: \"ConfigMap\"\n

    The above CR creates an Extension for the Tenant named tenant-sample with the following configurations:

    • onDeletePurgeAppProject: If set to true, the AppProject will be deleted when the Extension is deleted.
    • sourceRepos: List of repositories to sync with ArgoCD.
    • appProject: Configuration for the AppProject.
      • clusterResourceWhitelist: List of cluster-scoped resources to sync.
      • namespaceResourceBlacklist: List of namespace-scoped resources to ignore.

    In the backend, MTO will create an ArgoCD AppProject with the specified configurations.

    "},{"location":"crds-api-reference/integration-config.html","title":"Integration Config","text":"

    IntegrationConfig is used to configure settings of multi-tenancy for Multi Tenant Operator.

    apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  components:\n    console: true\n    showback: true\n    ingress:\n      ingressClassName: 'nginx'\n      keycloak:\n        host: tenant-operator-keycloak.apps.mycluster-ams.abcdef.cloud\n        tlsSecretName: tenant-operator-tls\n      console:\n        host: tenant-operator-console.apps.mycluster-ams.abcdef.cloud\n        tlsSecretName: tenant-operator-tls\n      gateway:\n        host: tenant-operator-gateway.apps.mycluster-ams.abcdef.cloud\n        tlsSecretName: tenant-operator-tls\n      customPricingModel:\n        CPU: \"0.031611\"\n        spotCPU: \"0.006655\"\n        RAM: \"0.004237\"\n        spotRAM: \"0.000892\"\n        GPU: \"0.95\"\n        storage: \"0.00005479452\"\n        zoneNetworkEgress: \"0.01\"\n        regionNetworkEgress: \"0.01\"\n        internetNetworkEgress: \"0.12\"\n  accessControl:\n    rbac:\n      tenantRoles:\n        default:\n          owner:\n            clusterRoles:\n              - admin\n          editor:\n            clusterRoles:\n              - edit\n          viewer:\n            clusterRoles:\n              - view\n        custom:\n        - labelSelector:\n            matchExpressions:\n            - key: stakater.com/kind\n              operator: In\n              values:\n                - build\n            matchLabels:\n              stakater.com/kind: dev\n          owner:\n            clusterRoles:\n              - custom-owner\n          editor:\n            clusterRoles:\n              - custom-editor\n          viewer:\n            clusterRoles:\n              - custom-view\n    namespaceAccessPolicy:\n      deny:\n        privilegedNamespaces:\n          users:\n            - system:serviceaccount:openshift-argocd:argocd-application-controller\n            - adam@stakater.com\n          groups:\n            - cluster-admins\n    privileged:\n      namespaces:\n        - ^default$\n        - ^openshift.*\n        - ^kube.*\n      serviceAccounts:\n        - ^system:serviceaccount:openshift.*\n        - ^system:serviceaccount:kube.*\n      users:\n        - ''\n      groups:\n        - cluster-admins\n  metadata:\n    groups:\n      labels:\n        role: customer-reader\n      annotations: \n        openshift.io/node-selector: node-role.kubernetes.io/worker=\n    namespaces:\n      labels:\n        stakater.com/workload-monitoring: \"true\"\n      annotations:\n        openshift.io/node-selector: node-role.kubernetes.io/worker=\n    sandboxes:\n      labels:\n        stakater.com/kind: sandbox\n      annotations:\n        openshift.io/node-selector: node-role.kubernetes.io/worker=\n  integrations:\n    keycloak:\n      realm: mto\n      address: https://keycloak.apps.prod.abcdefghi.kubeapp.cloud\n      clientName: mto-console\n    argocd:\n      clusterResourceWhitelist:\n        - group: tronador.stakater.com\n          kind: EnvironmentProvisioner\n      namespaceResourceBlacklist:\n        - group: '' # all groups\n          kind: ResourceQuota\n      namespace: openshift-operators\n    vault:\n      enabled: true\n      authMethod: kubernetes      #enum: {kubernetes:default, token}\n      accessInfo: \n        accessorPath: oidc/\n        address: https://vault.apps.prod.abcdefghi.kubeapp.cloud/\n        roleName: mto\n        secretRef:       \n          name: ''\n          namespace: ''\n      config: \n        ssoClient: vault\n

    Following are the different components that can be used to configure multi-tenancy in a cluster via Multi Tenant Operator.

    "},{"location":"crds-api-reference/integration-config.html#components","title":"Components","text":"
      components:\n    console: true\n    showback: true\n    ingress:\n      ingressClassName: nginx\n      keycloak:\n        host: tenant-operator-keycloak.apps.mycluster-ams.abcdef.cloud\n        tlsSecretName: tenant-operator-tls\n      console:\n        host: tenant-operator-console.apps.mycluster-ams.abcdef.cloud\n        tlsSecretName: tenant-operator-tls\n      gateway:\n        host: tenant-operator-gateway.apps.mycluster-ams.abcdef.cloud\n        tlsSecretName: tenant-operator-tls\n
    • components.console: Enables or disables the console GUI for MTO.
    • components.showback: Enables or disables the showback feature on the console.
    • components.ingress: Configures the ingress settings for various components:
      • ingressClassName: Ingress class to be used for the ingress.
      • console: Settings for the console's ingress.
        • host: hostname for the console's ingress.
        • tlsSecretName: Name of the secret containing the TLS certificate and key for the console's ingress.
      • gateway: Settings for the gateway's ingress.
        • host: hostname for the gateway's ingress.
        • tlsSecretName: Name of the secret containing the TLS certificate and key for the gateway's ingress.
      • keycloak: Settings for the Keycloak's ingress.
        • host: hostname for the Keycloak's ingress.
        • tlsSecretName: Name of the secret containing the TLS certificate and key for the Keycloak's ingress.

    Here's an example of how to generate the secrets required to configure MTO:

    TLS Secret for Ingress:

    Create a TLS secret containing your SSL/TLS certificate and key for secure communication. This secret will be used for the Console, Gateway, and Keycloak ingresses.

    kubectl -n multi-tenant-operator create secret tls <tls-secret-name> --key=<path-to-key.pem> --cert=<path-to-cert.pem>\n

    Integration config will be managing the following resources required for console GUI:

    • MTO Postgresql resources.
    • MTO Prometheus resources.
    • MTO Opencost resources.
    • MTO Console, Gateway, Keycloak resources.
    • Showback cronjob.

    Details on console GUI and showback can be found here

    "},{"location":"crds-api-reference/integration-config.html#access-control","title":"Access Control","text":"
    accessControl:\n  rbac:\n    tenantRoles:\n      default:\n        owner:\n          clusterRoles:\n            - admin\n        editor:\n          clusterRoles:\n            - edit\n        viewer:\n          clusterRoles:\n            - view\n      custom:\n      - labelSelector:\n          matchExpressions:\n          - key: stakater.com/kind\n            operator: In\n            values:\n              - build\n          matchLabels:\n            stakater.com/kind: dev\n        owner:\n          clusterRoles:\n            - custom-owner\n        editor:\n          clusterRoles:\n            - custom-editor\n        viewer:\n          clusterRoles:\n            - custom-view\n  namespaceAccessPolicy:\n    deny:\n      privilegedNamespaces:\n        users:\n          - system:serviceaccount:openshift-argocd:argocd-application-controller\n          - adam@stakater.com\n          groups:\n            - cluster-admins\n  privileged:\n    namespaces:\n      - ^default$\n      - ^openshift.*\n      - ^kube.*\n    serviceAccounts:\n      - ^system:serviceaccount:openshift.*\n      - ^system:serviceaccount:kube.*\n    users:\n      - ''\n    groups:\n      - cluster-admins\n
    "},{"location":"crds-api-reference/integration-config.html#rbac","title":"RBAC","text":"

    RBAC is used to configure the roles that will be applied to each Tenant namespace. The field allows optional custom roles, that are then used to create RoleBindings for namespaces that match a labelSelector.

    "},{"location":"crds-api-reference/integration-config.html#tenantroles","title":"TenantRoles","text":"

    TenantRoles are required within the IntegrationConfig, as they are used for defining what roles will be applied to each Tenant namespace. The field allows optional custom roles, that are then used to create RoleBindings for namespaces that match a labelSelector.

    \u26a0\ufe0f If you do not configure roles in any way, then the default OpenShift roles of owner, edit, and view will apply to Tenant members. Their details can be found here

    rbac:\n  tenantRoles:\n    default:\n      owner:\n        clusterRoles:\n          - admin\n      editor:\n        clusterRoles:\n          - edit\n      viewer:\n        clusterRoles:\n          - view\n    custom:\n    - labelSelector:\n        matchExpressions:\n        - key: stakater.com/kind\n          operator: In\n          values:\n            - build\n        matchLabels:\n          stakater.com/kind: dev\n      owner:\n        clusterRoles:\n          - custom-owner\n      editor:\n        clusterRoles:\n          - custom-editor\n      viewer:\n        clusterRoles:\n          - custom-view\n
    "},{"location":"crds-api-reference/integration-config.html#default","title":"Default","text":"

    This field contains roles that will be used to create default roleBindings for each namespace that belongs to tenants. These roleBindings are only created for a namespace if that namespace isn't already matched by the custom field below it. Therefore, it is required to have at least one role mentioned within each of its three subfields: owner, editor, and viewer. These 3 subfields also correspond to the member fields of the Tenant CR

    "},{"location":"crds-api-reference/integration-config.html#custom","title":"Custom","text":"

    An array of custom roles. Similar to the default field, you can mention roles within this field as well. However, the custom roles also require the use of a labelSelector for each iteration within the array. The roles mentioned here will only apply to the namespaces that are matched by the labelSelector. If a namespace is matched by 2 different labelSelectors, then both roles will apply to it. Additionally, roles can be skipped within the labelSelector. These missing roles are then inherited from the default roles field . For example, if the following custom roles arrangement is used:

    custom:\n- labelSelector:\n    matchExpressions:\n    - key: stakater.com/kind\n      operator: In\n      values:\n        - build\n    matchLabels:\n      stakater.com/kind: dev\n  owner:\n    clusterRoles:\n      - custom-owner\n

    Then the editor and viewer roles will be taken from the default roles field, as that is required to have at least one role mentioned.

    "},{"location":"crds-api-reference/integration-config.html#namespace-access-policy","title":"Namespace Access Policy","text":"

    Namespace Access Policy is used to configure the namespaces that are allowed to be created by tenants. It also allows the configuration of namespaces that are ignored by MTO.

    namespaceAccessPolicy:\n  deny:\n    privilegedNamespaces:\n      groups:\n        - cluster-admins\n      users:\n        - system:serviceaccount:openshift-argocd:argocd-application-controller\n        - adam@stakater.com\n  privileged:\n    namespaces:\n      - ^default$\n      - ^openshift.*\n      - ^kube.*\n    serviceAccounts:\n      - ^system:serviceaccount:openshift.*\n      - ^system:serviceaccount:kube.*\n    users:\n      - ''\n    groups:\n      - cluster-admins\n
    "},{"location":"crds-api-reference/integration-config.html#deny","title":"Deny","text":"

    namespaceAccessPolicy.Deny: Can be used to restrict privileged users/groups CRUD operation over managed namespaces.

    "},{"location":"crds-api-reference/integration-config.html#privileged","title":"Privileged","text":""},{"location":"crds-api-reference/integration-config.html#namespaces","title":"Namespaces","text":"

    privileged.namespaces: Contains the list of namespaces ignored by MTO. MTO will not manage the namespaces in this list. Treatment for privileged namespaces does not involve further integrations or finalizers processing as with normal namespaces. Values in this list are regex patterns.

    For example:

    • To ignore the default namespace, we can specify ^default$
    • To ignore all namespaces starting with the openshift- prefix, we can specify ^openshift-.*.
    • To ignore any namespace containing stakater in its name, we can specify ^stakater.. (A constant word given as a regex pattern will match any namespace containing that word.)
    "},{"location":"crds-api-reference/integration-config.html#serviceaccounts","title":"ServiceAccounts","text":"

    privileged.serviceAccounts: Contains the list of ServiceAccounts ignored by MTO. MTO will not manage the ServiceAccounts in this list. Values in this list are regex patterns. For example, to ignore all ServiceAccounts starting with the system:serviceaccount:openshift- prefix, we can use ^system:serviceaccount:openshift-.*; and to ignore a specific service account like system:serviceaccount:builder service account we can use ^system:serviceaccount:builder$.

    Note

    stakater, stakater. and stakater.* will have the same effect. To check out the combinations, go to Regex101, select Golang, and type your expected regex and test string.

    "},{"location":"crds-api-reference/integration-config.html#users","title":"Users","text":"

    privileged.users: Contains the list of users ignored by MTO. MTO will not manage the users in this list. Values in this list are regex patterns.

    "},{"location":"crds-api-reference/integration-config.html#groups","title":"Groups","text":"

    privileged.groups: Contains names of the groups that are allowed to perform CRUD operations on namespaces present on the cluster. Users in the specified group(s) will be able to perform these operations without MTO getting in their way. MTO does not interfere even with the deletion of privilegedNamespaces.

    Note

    User kube:admin is bypassed by default to perform operations as a cluster admin, this includes operations on all the namespaces.

    \u26a0\ufe0f If you want to use a more complex regex pattern (for the privileged.namespaces or privileged.serviceAccounts field), it is recommended that you test the regex pattern first - either locally or using a platform such as https://regex101.com/.

    "},{"location":"crds-api-reference/integration-config.html#metadata","title":"Metadata","text":"
    metadata:\n  groups:\n    labels:\n      role: customer-reader\n    annotations: {}\n  namespaces:\n    labels:\n      stakater.com/workload-monitoring: \"true\"\n    annotations:\n      openshift.io/node-selector: node-role.kubernetes.io/worker=\n  sandboxes:\n    labels:\n      stakater.com/kind: sandbox\n    annotations: {}\n
    "},{"location":"crds-api-reference/integration-config.html#namespaces-group-and-sandbox","title":"Namespaces, group and sandbox","text":"

    We can use the metadata.namespaces, metadata.group and metadata.sandbox fields to automatically add labels and annotations to the Namespaces and Groups managed via MTO.

    If we want to add default labels/annotations to sandbox namespaces of tenants than we just simply add them in metadata.namespaces.labels/metadata.namespaces.annotations respectively.

    Whenever a project is made it will have the labels and annotations as mentioned above.

    kind: Project\napiVersion: project.openshift.io/v1\nmetadata:\n  name: bluesky-build\n  annotations:\n    openshift.io/node-selector: node-role.kubernetes.io/worker=\n  labels:\n    workload-monitoring: 'true'\n    stakater.com/tenant: bluesky\nspec:\n  finalizers:\n    - kubernetes\nstatus:\n  phase: Active\n
    kind: Group\napiVersion: user.openshift.io/v1\nmetadata:\n  name: bluesky-owner-group\n  labels:\n    role: customer-reader\nusers:\n  - andrew@stakater.com\n
    "},{"location":"crds-api-reference/integration-config.html#integrations","title":"Integrations","text":"

    Integrations are used to configure the integrations that MTO has with other tools. Currently, MTO supports the following integrations:

    integrations:\n  keycloak:\n    realm: mto\n    address: https://keycloak.apps.prod.abcdefghi.kubeapp.cloud\n    clientName: mto-console\n  argocd:\n    clusterResourceWhitelist:\n      - group: tronador.stakater.com\n        kind: EnvironmentProvisioner\n    namespaceResourceBlacklist:\n      - group: '' # all groups\n        kind: ResourceQuota\n    namespace: openshift-operators\n  vault:\n    enabled: true\n    authMethod: kubernetes      #enum: {kubernetes:default, Token}\n    accessInfo: \n      accessorPath: oidc/\n      address: https://vault.apps.prod.abcdefghi.kubeapp.cloud/\n      roleName: mto\n      secretRef:       \n        name: ''\n        namespace: ''\n    config: \n      ssoClient: vault\n
    "},{"location":"crds-api-reference/integration-config.html#keycloak","title":"Keycloak","text":"

    Keycloak is an open-source Identity and Access Management solution aimed at modern applications and services. It makes it easy to secure applications and services with little to no code.

    If a Keycloak instance is already set up within your cluster, configure it for MTO by enabling the following configuration:

    keycloak:\n  realm: mto\n  address: https://keycloak.apps.prod.abcdefghi.kubeapp.cloud/\n  clientName: mto-console\n
    • keycloak.realm: The realm in Keycloak where the client is configured.
    • keycloak.address: The address of the Keycloak instance.
    • keycloak.clientName: The name of the client in Keycloak.

    For more details around enabling Keycloak in MTO, visit here

    "},{"location":"crds-api-reference/integration-config.html#argocd","title":"ArgoCD","text":"

    ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes. It follows the GitOps pattern of using Git repositories as the source of truth for defining the desired application state. ArgoCD uses Kubernetes manifests and configures the applications on the cluster.

    If argocd is configured on a cluster, then ArgoCD configuration can be enabled.

    argocd:\n  enabled: bool\n  clusterResourceWhitelist:\n    - group: tronador.stakater.com\n      kind: EnvironmentProvisioner\n  namespaceResourceBlacklist:\n    - group: '' # all groups\n      kind: ResourceQuota\n  namespace: openshift-operators\n
    • argocd.clusterResourceWhitelist allows ArgoCD to sync the listed cluster scoped resources from your GitOps repo.
    • argocd.namespaceResourceBlacklist prevents ArgoCD from syncing the listed resources from your GitOps repo.
    • argocd.namespace is an optional field used to specify the namespace where ArgoCD Applications and AppProjects are deployed. The field should be populated when you want to create an ArgoCD AppProject for each tenant.
    "},{"location":"crds-api-reference/integration-config.html#vault","title":"Vault","text":"

    Vault is used to secure, store and tightly control access to tokens, passwords, certificates, encryption keys for protecting secrets and other sensitive data using a UI, CLI, or HTTP API.

    If vault is configured on a cluster, then Vault configuration can be enabled.

    vault:\n  enabled: true\n  authMethod: kubernetes      #enum: {kubernetes:default, token}\n  accessInfo: \n    accessorPath: oidc/\n    address: https://vault.apps.prod.abcdefghi.kubeapp.cloud/\n    roleName: mto\n    secretRef:\n      name: ''\n      namespace: ''\n  config: \n    ssoClient: vault\n

    If enabled, then admins have to specify the authMethod to be used for authentication. MTO supports two authentication methods:

    • kubernetes: This is the default authentication method. It uses the Kubernetes authentication method to authenticate with Vault.
    • token: This method uses a Vault token to authenticate with Vault.
    "},{"location":"crds-api-reference/integration-config.html#authmethod-kubernetes","title":"AuthMethod - Kubernetes","text":"

    If authMethod is set to kubernetes, then admins have to specify the following fields:

    • accessorPath: Accessor Path within Vault to fetch SSO accessorID
    • address: Valid Vault address reachable within cluster.
    • roleName: Vault's Kubernetes authentication role
    • sso.clientName: SSO client name.
    "},{"location":"crds-api-reference/integration-config.html#authmethod-token","title":"AuthMethod - Token","text":"

    If authMethod is set to token, then admins have to specify the following fields:

    • accessorPath: Accessor Path within Vault to fetch SSO accessorID
    • address: Valid Vault address reachable within cluster.
    • secretRef: Secret containing Vault token.
      • name: Name of the secret containing Vault token.
      • namespace: Namespace of the secret containing Vault token.

    For more details around enabling Kubernetes auth in Vault, visit here

    The role created within Vault for Kubernetes authentication should have the following permissions:

    path \"secret/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"sys/mounts\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"sys/mounts/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"managed-addons/*\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"auth/kubernetes/role/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"sys/auth\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"sys/policies/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"identity/group\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"identity/group-alias\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"identity/group/name/*\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"identity/group/id/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\n
    "},{"location":"crds-api-reference/integration-config.html#custom-pricing-model","title":"Custom Pricing Model","text":"

    You can modify IntegrationConfig to customise the default pricing model. Here is what you need at IntegrationConfig.spec.components:

    components:\n    console: true # should be enabled\n    showback: true # should be enabled\n    # add below and override any default value\n    # you can also remove the ones you do not need\n    customPricingModel:\n        CPU: \"0.031611\"\n        spotCPU: \"0.006655\"\n        RAM: \"0.004237\"\n        spotRAM: \"0.000892\"\n        GPU: \"0.95\"\n        storage: \"0.00005479452\"\n        zoneNetworkEgress: \"0.01\"\n        regionNetworkEgress: \"0.01\"\n        internetNetworkEgress: \"0.12\"\n

    After modifying your default IntegrationConfig in multi-tenant-operator namespace, a configmap named opencost-custom-pricing will be updated. You will be able to see updated pricing info in mto-console.

    "},{"location":"crds-api-reference/quota.html","title":"Quota","text":"

    Using Multi Tenant Operator, the cluster-admin can set and enforce cluster resource quotas and limit ranges for tenants.

    "},{"location":"crds-api-reference/quota.html#assigning-resource-quotas","title":"Assigning Resource Quotas","text":"

    Bill is a cluster admin who will first create Quota CR where he sets the maximum resource limits that Anna's tenant will have. Here limitrange is an optional field, cluster admin can skip it if not needed.

    kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta1\nkind: Quota\nmetadata:\n  name: small\nspec:\n  resourcequota:\n    hard:\n      requests.cpu: '5'\n      requests.memory: '5Gi'\n      configmaps: \"10\"\n      secrets: \"10\"\n      services: \"10\"\n      services.loadbalancers: \"2\"\n  limitrange:\n    limits:\n      - type: \"Pod\"\n        max:\n          cpu: \"2\"\n          memory: \"1Gi\"\n        min:\n          cpu: \"200m\"\n          memory: \"100Mi\"\nEOF\n

    For more details please refer to Quotas.

    kubectl get quota small\nNAME       STATE    AGE\nsmall      Active   3m\n

    Bill then proceeds to create a tenant for Anna, while also linking the newly created Quota.

    kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n  namespaces:\n    sandboxes:\n      enabled: true\nEOF\n

    Now that the quota is linked with Anna's tenant, Anna can create any resource within the values of resource quota and limit range.

    kubectl -n bluesky-production create deployment nginx --image nginx:latest --replicas 4\n

    Once the resource quota assigned to the tenant has been reached, Anna cannot create further resources.

    kubectl create pods bluesky-training\nError from server (Cannot exceed Namespace quota: please, reach out to the system administrators)\n
    "},{"location":"crds-api-reference/quota.html#limiting-persistentvolume-for-tenant","title":"Limiting PersistentVolume for Tenant","text":"

    Bill, as a cluster admin, wants to restrict the amount of storage a Tenant can use. For that he'll add the requests.storage field to quota.spec.resourcequota.hard. If Bill wants to restrict tenant bluesky to use only 50Gi of storage, he'll first create a quota with requests.storage field set to 50Gi.

    kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta1\nkind: Quota\nmetadata:\n  name: medium\nspec:\n  resourcequota:\n    hard:\n      requests.cpu: '5'\n      requests.memory: '10Gi'\n      requests.storage: '50Gi'\n

    Once the quota is created, Bill will create the tenant and set the quota field to the one he created.

    kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n  namespaces:\n    sandboxes:\n      enabled: true\nEOF\n

    Now, the combined storage used by all tenant namespaces will not exceed 50Gi.

    "},{"location":"crds-api-reference/quota.html#adding-storageclass-restrictions-for-tenant","title":"Adding StorageClass Restrictions for Tenant","text":"

    Now, Bill, as a cluster admin, wants to make sure that no Tenant can provision more than a fixed amount of storage from a StorageClass. Bill can restrict that using <storage-class-name>.storageclass.storage.k8s.io/requests.storage field in quota.spec.resourcequota.hard field. If Bill wants to restrict tenant sigma to use only 20Gi of storage from storage class stakater, he'll first create a StorageClass stakater and then create the relevant Quota with stakater.storageclass.storage.k8s.io/requests.storage field set to 20Gi.

    kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta1\nkind: Quota\nmetadata:\n  name: small\nspec:\n  resourcequota:\n    hard:\n      requests.cpu: '2'\n      requests.memory: '4Gi'\n      stakater.storageclass.storage.k8s.io/requests.storage: '20Gi'\n

    Once the quota is created, Bill will create the tenant and set the quota field to the one he created.

    kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n  namespaces:\n    sandboxes:\n      enabled: true\nEOF\n

    Now, the combined storage provisioned from StorageClass stakater used by all tenant namespaces will not exceed 20Gi.

    The 20Gi limit will only be applied to StorageClass stakater. If a tenant member creates a PVC with some other StorageClass, he will not be restricted.

    Tip

    More details about Resource Quota can be found here

    "},{"location":"crds-api-reference/template-group-instance.html","title":"TemplateGroupInstance","text":"

    Cluster scoped resource:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: namespace-parameterized-restrictions-tgi\nspec:\n  template: namespace-parameterized-restrictions\n  sync: true\n  selector:\n    matchExpressions:\n    - key: stakater.com/tenant\n      operator: In\n      values:\n        - alpha\n        - beta\nparameters:\n  - name: CIDR_IP\n    value: \"172.17.0.0/16\"\n

    TemplateGroupInstance distributes a template across multiple namespaces which are selected by labelSelector.

    "},{"location":"crds-api-reference/template-instance.html","title":"TemplateInstance","text":"

    Namespace scoped resource:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateInstance\nmetadata:\n  name: networkpolicy\n  namespace: build\nspec:\n  template: networkpolicy\n  sync: true\nparameters:\n  - name: CIDR_IP\n    value: \"172.17.0.0/16\"\n

    TemplateInstance are used to keep track of resources created from Templates, which are being instantiated inside a Namespace. Generally, a TemplateInstance is created from a Template and then the TemplateInstances will not be updated when the Template changes later on. To change this behavior, it is possible to set spec.sync: true in a TemplateInstance. Setting this option, means to keep this TemplateInstance in sync with the underlying template (similar to Helm upgrade).

    "},{"location":"crds-api-reference/template.html","title":"Template","text":""},{"location":"crds-api-reference/template.html#cluster-scoped-resource","title":"Cluster scoped resource","text":"
    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: redis\nresources:\n  helm:\n    releaseName: redis\n    chart:\n      repository:\n        name: redis\n        repoUrl: https://charts.bitnami.com/bitnami\n    values: |\n      redisPort: 6379\n---\napiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: networkpolicy\nparameters:\n  - name: CIDR_IP\n    value: \"172.17.0.0/16\"\nresources:\n  manifests:\n    - kind: NetworkPolicy\n      apiVersion: networking.k8s.io/v1\n      metadata:\n        name: deny-cross-ns-traffic\n      spec:\n        podSelector:\n          matchLabels:\n            role: db\n        policyTypes:\n        - Ingress\n        - Egress\n        ingress:\n        - from:\n          - ipBlock:\n              cidr: \"${{CIDR_IP}}\"\n              except:\n              - 172.17.1.0/24\n          - namespaceSelector:\n              matchLabels:\n                project: myproject\n          - podSelector:\n              matchLabels:\n                role: frontend\n          ports:\n          - protocol: TCP\n            port: 6379\n        egress:\n        - to:\n          - ipBlock:\n              cidr: 10.0.0.0/24\n          ports:\n          - protocol: TCP\n            port: 5978\n---\napiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: resource-mapping\nresources:\n  resourceMappings:\n    secrets:\n      - name: secret-s1\n        namespace: namespace-n1\n    configMaps:\n      - name: configmap-c1\n        namespace: namespace-n2\n

    Templates are used to initialize Namespaces, share common resources across namespaces, and map secrets/configmaps from one namespace to other namespaces.

    • They either contain one or more Kubernetes manifests, a reference to secrets/configmaps, or a Helm chart.
    • They are being tracked by TemplateInstances in each Namespace they are applied to.
    • They can contain pre-defined parameters such as ${namespace}/${tenant} or user-defined ${MY_PARAMETER} that can be specified within an TemplateInstance.

    Also, you can define custom variables in Template and TemplateInstance . The parameters defined in TemplateInstance are overwritten the values defined in Template .

    Manifest Templates: The easiest option to define a Template is by specifying an array of Kubernetes manifests which should be applied when the Template is being instantiated.

    Helm Chart Templates: Instead of manifests, a Template can specify a Helm chart that will be installed (using Helm template) when the Template is being instantiated.

    Resource Mapping Templates: A template can be used to map secrets and configmaps from one tenant's namespace to another tenant's namespace, or within a tenant's namespace.

    "},{"location":"crds-api-reference/template.html#mandatory-and-optional-templates","title":"Mandatory and Optional Templates","text":"

    Templates can either be mandatory or optional. By default, all Templates are optional. Cluster Admins can make Templates mandatory by adding them to the spec.templateInstances array within the Tenant configuration. All Templates listed in spec.templateInstances will always be instantiated within every Namespace that is created for the respective Tenant.

    "},{"location":"crds-api-reference/tenant.html","title":"Tenant","text":"

    A minimal Tenant definition requires only a quota field, essential for limiting resource consumption:

    apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: alpha\nspec:\n  quota: small\n

    For a more comprehensive setup, a detailed Tenant definition includes various configurations:

    apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: tenant-sample\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - kubeadmin\n      groups:\n        - admin-group\n    editors:\n      users:\n        - devuser1\n        - devuser2\n      groups:\n        - dev-group\n    viewers:\n      users:\n        - viewuser\n      groups:\n        - view-group\n  hibernation:\n  # UTC time\n    sleepSchedule: \"20 * * * *\"\n    wakeSchedule: \"40 * * * *\"        \n  namespaces:\n    sandboxes:\n      enabled: true\n      private: true\n    withoutTenantPrefix:\n      - analytics\n      - marketing\n    withTenantPrefix:\n      - dev\n      - staging\n    onDeletePurgeNamespaces: true\n    metadata:\n      common:\n        labels:\n          common-label: common-value\n        annotations:\n          common-annotation: common-value\n      sandbox:\n        labels:\n          sandbox-label: sandbox-value\n        annotations:\n          sandbox-annotation: sandbox-value\n      specific:\n        - namespaces:\n            - tenant-sample-dev\n          labels:\n            specific-label: specific-dev-value\n          annotations:\n            specific-annotation: specific-dev-value\n  desc: \"This is a sample tenant setup for the v1beta3 version.\"\n
    "},{"location":"crds-api-reference/tenant.html#access-control","title":"Access Control","text":"

    Structured access control is critical for managing roles and permissions within a tenant effectively. It divides users into three categories, each with customizable privileges. This design enables precise role-based access management.

    These roles are obtained from IntegrationConfig's TenantRoles field.

    • Owners: Have full administrative rights, including resource management and namespace creation. Their roles are crucial for high-level management tasks.
    • Editors: Granted permissions to modify resources, enabling them to support day-to-day operations without full administrative access.
    • Viewers: Provide read-only access, suitable for oversight and auditing without the ability to alter resources.

    Users and groups are linked to these roles by specifying their usernames or group names in the respective fields under owners, editors, and viewers.

    "},{"location":"crds-api-reference/tenant.html#quota","title":"Quota","text":"

    The quota field sets the resource limits for the tenant, such as CPU and memory usage, to prevent any single tenant from consuming a disproportionate amount of resources. This mechanism ensures efficient resource allocation and fosters fair usage practices across all tenants.

    For more information on quotas, please refer here.

    "},{"location":"crds-api-reference/tenant.html#namespaces","title":"Namespaces","text":"

    Controls the creation and management of namespaces within the tenant:

    • sandboxes:

      • When enabled, sandbox namespaces are created with the following naming convention - {TenantName}-{UserName}-sandbox.
      • In case of groups, the sandbox namespaces will be created for each member of the group.
      • Setting private to true will make the sandboxes visible only to the user they belong to. By default, sandbox namespaces are visible to all tenant members.
    • withoutTenantPrefix: Lists the namespaces to be created without automatically prefixing them with the tenant name, useful for shared or common resources.

    • withTenantPrefix: Namespaces listed here will be prefixed with the tenant name, ensuring easy identification and isolation.
    • onDeletePurgeNamespaces: Determines whether namespaces associated with the tenant should be deleted upon the tenant's deletion, enabling clean up and resource freeing.
    • metadata: Configures metadata like labels and annotations that are applied to namespaces managed by the tenant:
      • common: Applies specified labels and annotations across all namespaces within the tenant, ensuring consistent metadata for resources and workloads.
      • sandbox: Special metadata for sandbox namespaces, which can include templated annotations or labels for dynamic information.
        • We also support the use of a templating mechanism within annotations, specifically allowing the inclusion of the tenant's username through the placeholder {{ TENANT.USERNAME }}. This template can be utilized to dynamically insert the tenant's username value into annotations, for example, as username: {{ TENANT.USERNAME }}.
      • specific: Allows applying unique labels and annotations to specified tenant namespaces, enabling custom configurations for particular workloads or environments.
    "},{"location":"crds-api-reference/tenant.html#hibernation","title":"Hibernation","text":"

    hibernation allows for the scheduling of inactive periods for namespaces associated with the tenant, effectively putting them into a \"sleep\" mode. This capability is designed to conserve resources during known periods of inactivity.

    • Configuration for this feature involves two key fields, sleepSchedule and wakeSchedule, both of which accept strings formatted according to cron syntax.
    • These schedules dictate when the namespaces will automatically transition into and out of hibernation, aligning resource usage with actual operational needs.
    "},{"location":"crds-api-reference/tenant.html#description","title":"Description","text":"

    desc provides a human-readable description of the tenant, aiding in documentation and at-a-glance understanding of the tenant's purpose and configuration.

    \u26a0\ufe0f If same label or annotation key is being applied using different methods provided, then the highest precedence will be given to namespaces.metadata.specific followed by namespaces.metadata.common and in the end would be the ones applied from openshift.project.labels/openshift.project.annotations in IntegrationConfig

    "},{"location":"explanation/console.html","title":"MTO Console","text":""},{"location":"explanation/console.html#introduction","title":"Introduction","text":"

    The Multi Tenant Operator (MTO) Console is a comprehensive user interface designed for both administrators and tenant users to manage multi-tenant environments. The MTO Console simplifies the complexity involved in handling various aspects of tenants and their related resources.

    "},{"location":"explanation/console.html#dashboard-overview","title":"Dashboard Overview","text":"

    The dashboard serves as a centralized monitoring hub, offering insights into the current state of tenants, namespaces, and quotas. It is designed to provide a quick summary/snapshot of MTO resources' status. Additionally, it includes a Showback graph that presents a quick glance of the seven-day cost trends associated with the namespaces/tenants based on the logged-in user.

    By default, MTO Console will be disabled and has to be enabled by setting the below configuration in IntegrationConfig.

    components:\n    console: true\n    ingress:\n      ingressClassName: <ingress-class-name>\n      console:\n        host: tenant-operator-console.<hostname>\n        tlsSecretName: <tls-secret-name>\n      gateway:\n        host: tenant-operator-gateway.<hostname>\n        tlsSecretName: <tls-secret-name>\n      keycloak:\n        host: tenant-operator-keycloak.<hostname>\n        tlsSecretName: <tls-secret-name>\n    showback: true\n    trustedRootCert: <root-ca-secret-name>\n

    <hostname> : hostname of the cluster <ingress-class-name> : name of the ingress class <tls-secret-name> : name of the secret that contains the TLS certificate and key <root-ca-secret-name> : name of the secret that contains the root CA certificate

    Note: trustedRootCert and tls-secret-name are optional. If not provided, MTO will use the default root CA certificate and secrets respectively.

    Once the above configuration is set on the IntegrationConfig, MTO would start provisioning the required resources for MTO Console to be ready. In a few moments, you should be able to see the Console Ingress in the multi-tenant-operator namespace which gives you access to the Console.

    For more details on the configuration, please visit here.

    "},{"location":"explanation/console.html#tenants","title":"Tenants","text":"

    Here, admins have a bird's-eye view of all tenants, with the ability to delve into each one for detailed examination and management. This section is pivotal for observing the distribution and organization of tenants within the system. More information on each tenant can be accessed by clicking the view option against each tenant name.

    "},{"location":"explanation/console.html#namespaces","title":"Namespaces","text":"

    Users can view all the namespaces that belong to their tenant, offering a comprehensive perspective of the accessible namespaces for tenant members. This section also provides options for detailed exploration.

    "},{"location":"explanation/console.html#quotas","title":"Quotas","text":"

    MTO's Quotas are crucial for managing resource allocation. In this section, administrators can assess the quotas assigned to each tenant, ensuring a balanced distribution of resources in line with operational requirements.

    "},{"location":"explanation/console.html#templates","title":"Templates","text":"

    The Templates section acts as a repository for standardized resource deployment patterns, which can be utilized to maintain consistency and reliability across tenant environments. Few examples include provisioning specific k8s manifests, helm charts, secrets or configmaps across a set of namespaces.

    "},{"location":"explanation/console.html#showback","title":"Showback","text":"

    The Showback feature is an essential financial governance tool, providing detailed insights into the cost implications of resource usage by tenant or namespace or other filters. This facilitates a transparent cost management and internal chargeback or showback process, enabling informed decision-making regarding resource consumption and budgeting.

    "},{"location":"explanation/console.html#user-roles-and-permissions","title":"User Roles and Permissions","text":""},{"location":"explanation/console.html#administrators","title":"Administrators","text":"

    Administrators have overarching access to the console, including the ability to view all namespaces and tenants. They have exclusive access to the IntegrationConfig, allowing them to view all the settings and integrations.

    "},{"location":"explanation/console.html#tenant-users","title":"Tenant Users","text":"

    Regular tenant users can monitor and manage their allocated resources. However, they do not have access to the IntegrationConfig and cannot view resources across different tenants, ensuring data privacy and operational integrity.

    "},{"location":"explanation/console.html#live-yaml-configuration-and-graph-view","title":"Live YAML Configuration and Graph View","text":"

    In the MTO Console, each resource section is equipped with a \"View\" button, revealing the live YAML configuration for complete information on the resource. For Tenant resources, a supplementary \"Graph\" option is available, illustrating the relationships and dependencies of all resources under a Tenant. This dual-view approach empowers users with both the detailed control of YAML and the holistic oversight of the graph view.

    You can find more details on graph visualization here: Graph Visualization

    "},{"location":"explanation/console.html#caching-and-database","title":"Caching and Database","text":"

    MTO integrates a dedicated database to streamline resource management. Now, all resources managed by MTO are efficiently stored in a Postgres database, enhancing the MTO Console's ability to efficiently retrieve all the resources for optimal presentation.

    The implementation of this feature is facilitated by the Bootstrap controller, streamlining the deployment process. This controller creates the PostgreSQL Database, establishes a service for inter-pod communication, and generates a secret to ensure secure connectivity to the database.

    Furthermore, the introduction of a dedicated cache layer ensures that there is no added burden on the Kube API server when responding to MTO Console requests. This enhancement not only improves response times but also contributes to a more efficient and responsive resource management system.

    "},{"location":"explanation/console.html#authentication-and-authorization","title":"Authentication and Authorization","text":""},{"location":"explanation/console.html#keycloak-for-authentication","title":"Keycloak for Authentication","text":"

    MTO Console incorporates Keycloak, a leading authentication module, to manage user access securely and efficiently. Keycloak is provisioned automatically by our controllers, setting up a new realm, client, and a default user named mto.

    "},{"location":"explanation/console.html#benefits","title":"Benefits","text":"
    • Industry Standard: Offers robust, reliable authentication in line with industry standards.
    • Integration with Existing Systems: Enables easy linkage with existing Active Directories or SSO systems, avoiding the need for redundant user management.
    • Administrative Control: Grants administrators full authority over user access to the console, enhancing security and operational integrity.
    "},{"location":"explanation/console.html#postgresql-as-persistent-storage-for-keycloak","title":"PostgreSQL as Persistent Storage for Keycloak","text":"

    MTO Console leverages PostgreSQL as the persistent storage solution for Keycloak, enhancing the reliability and flexibility of the authentication system.

    It offers benefits such as enhanced data reliability, easy data export and import.

    "},{"location":"explanation/console.html#benefits_1","title":"Benefits","text":"
    • Persistent Data Storage: By using PostgreSQL, Keycloak's data, including realms, clients, and user information, is preserved even in the event of a pod restart. This ensures continuous availability and stability of the authentication system.
    • Data Exportability: Customers can easily export Keycloak configurations and data from the PostgreSQL database.
    • Transferability Across Environments: The exported data can be conveniently imported into another cluster or Keycloak instance, facilitating smooth transitions and backups.
    • No Data Loss: Ensures that critical authentication data is not lost during system updates or maintenance.
    • Operational Flexibility: Provides customers with greater control over their authentication data, enabling them to manage and migrate their configurations as needed.
    "},{"location":"explanation/console.html#built-in-module-for-authorization","title":"Built-in module for Authorization","text":"

    The MTO Console is equipped with an authorization module, designed to manage access rights intelligently and securely.

    "},{"location":"explanation/console.html#benefits_2","title":"Benefits","text":"
    • User and Tenant Based: Authorization decisions are made based on the user's membership in specific tenants, ensuring appropriate access control.
    • Role-Specific Access: The module considers the roles assigned to users, granting permissions accordingly to maintain operational integrity.
    • Elevated Privileges for Admins: Users identified as administrators or members of the clusterAdminGroups are granted comprehensive permissions across the console.
    • Database Caching: Authorization decisions are cached in the database, reducing reliance on the Kubernetes API server.
    • Faster, Reliable Access: This caching mechanism ensures quicker and more reliable access for users, enhancing the overall responsiveness of the MTO Console.
    "},{"location":"explanation/console.html#conclusion","title":"Conclusion","text":"

    The MTO Console is engineered to simplify complex multi-tenant management. The current iteration focuses on providing comprehensive visibility. Future updates could include direct CUD (Create/Update/Delete) capabilities from the dashboard, enhancing the console\u2019s functionality. The Showback feature remains a standout, offering critical cost tracking and analysis. The delineation of roles between administrators and tenant users ensures a secure and organized operational framework.

    "},{"location":"explanation/logs-metrics.html","title":"Metrics and Logs Documentation","text":"

    This document offers an overview of the Prometheus metrics implemented by the multi_tenant_operator controllers, along with an interpretation guide for the logs and statuses generated by these controllers. Each metric is designed to provide specific insights into the controllers' operational performance, while the log interpretation guide aids in understanding their behavior and workflow processes. Additionally, the status descriptions for custom resources provide operational snapshots. Together, these elements form a comprehensive toolkit for monitoring and enhancing the performance and health of the controllers.

    "},{"location":"explanation/logs-metrics.html#metrics-list","title":"Metrics List","text":"

    multi_tenant_operator_resources_deployed_total

    • Description: Tracks the total number of resources deployed by the operator.
    • Type: Gauge
    • Labels: kind, name, namespace
    • Usage: Helps to understand the overall workload managed by the operator.

    multi_tenant_operator_resources_deployed

    • Description: Monitors resources currently deployed by the operator.
    • Type: Gauge
    • Labels: kind, name, namespace, type
    • Usage: Useful for tracking the current state and type of resources managed by the operator.

    multi_tenant_operator_reconcile_error

    • Description: Indicates resources in an error state, broken down by resource kind, name, and namespace.
    • Type: Gauge
    • Labels: kind, name, namespace, state, errors
    • Usage: Essential for identifying and analyzing errors in resource management.

    multi_tenant_operator_reconcile_count

    • Description: Counts the number of reconciliations performed for a template group instance, categorized by name.
    • Type: Gauge
    • Labels: kind, name
    • Usage: Provides insight into the frequency of reconciliation processes.

    multi_tenant_operator_reconcile_seconds

    • Description: Represents the cumulative duration, in seconds, taken to reconcile a template group instance, categorized by instance name.
    • Type: Gauge
    • Labels: kind, name
    • Usage: Critical for assessing the time efficiency of the reconciliation process.

    multi_tenant_operator_reconcile_seconds_total

    • Description: Tracks the total duration, in seconds, for all reconciliation processes of a template group instance, categorized by instance name.
    • Type: Gauge
    • Labels: kind, name
    • Usage: Useful for understanding the overall time spent on reconciliation processes.
    "},{"location":"explanation/logs-metrics.html#custom-resource-status","title":"Custom Resource Status","text":"

    In this section, we delve into the status of various custom resources managed by our controllers. The kubectl describe command can be used to fetch the status of these resources.

    "},{"location":"explanation/logs-metrics.html#template-group-instance","title":"Template Group Instance","text":"

    Status from the templategroupinstances.tenantoperator.stakater.com custom resource:

    • Current Operational State: Provides a snapshot of the resource's current condition.
    • Conditions: Offers a detailed view of the resource's status, which includes:
      • InstallSucceeded: Indicates the success of the instance's installation.
      • Ready: Shows the readiness of the instance, with details on the last reconciliation process, its duration, and relevant messages.
      • Running: Reports on active processes like ongoing resource reconciliation.
    • Deployed Namespaces: Enumerates the namespaces where the instance has been deployed, along with their statuses and associated template manifests.
    • Manifest Hashes: Includes the Template Manifests Hash and Resource Mapping Hash, which provide versioning and change tracking for template manifests and resource mappings.
    "},{"location":"explanation/logs-metrics.html#log-interpretation-guide","title":"Log Interpretation Guide","text":""},{"location":"explanation/logs-metrics.html#template-group-instance-controller","title":"Template Group Instance Controller","text":"

    Logs from the tenant-operator-templategroupinstance-controller:

    • Reconciliation Process: Logs starting with Reconciling! mark the beginning of a reconciliation process for a TemplateGroupInstance. Subsequent actions like Creating/Updating TemplateGroupInstance and Retrieving list of namespaces Matching to TGI outline the reconciliation steps.
    • Namespace and Resource Management: Logs such as Namespaces test-namespace-1 is new or failed... and Creating/Updating resource... detail the management of Kubernetes resources in specific namespaces.
    • Worker Activities: Logs labeled [Worker X] show tasks being processed in parallel, including steps like Validating parameters, Gathering objects from manifest, and Apply manifests.
    • Reconciliation Completion: Entries like End Reconciling and Defering XXth Reconciling, with duration XXXms indicate the end of a reconciliation process and its duration, aiding in performance analysis.
    • Watcher Events: Logs from Watcher such as Delete call received for object... and Following resource is recreated... are key for tracking changes to Kubernetes objects.

    These logs are crucial for tracking the system's behavior, diagnosing issues, and comprehending the resource management workflow.

    "},{"location":"explanation/multi-tenancy-vault.html","title":"Multi-Tenancy in Vault","text":""},{"location":"explanation/multi-tenancy-vault.html#vault-multitenancy","title":"Vault Multitenancy","text":"

    HashiCorp Vault is an identity-based secret and encryption management system. Vault validates and authorizes a system's clients (users, machines, apps) before providing them access to secrets or stored sensitive data.

    "},{"location":"explanation/multi-tenancy-vault.html#vault-integration-in-multi-tenant-operator","title":"Vault integration in Multi Tenant Operator","text":""},{"location":"explanation/multi-tenancy-vault.html#service-account-auth-in-vault","title":"Service Account Auth in Vault","text":"

    MTO enables the Kubernetes auth method which can be used to authenticate with Vault using a Kubernetes Service Account Token. When enabled, for every tenant namespace, MTO automatically creates policies and roles that allow the service accounts present in those namespaces to read secrets at tenant's path in Vault. The name of the role is the same as namespace name.

    These service accounts are required to have stakater.com/vault-access: true label, so they can be authenticated with Vault via MTO.

    The Diagram shows how MTO enables ServiceAccounts to read secrets from Vault.

    "},{"location":"explanation/multi-tenancy-vault.html#user-oidc-auth-in-vault","title":"User OIDC Auth in Vault","text":"

    This requires a running RHSSO(RedHat Single Sign On) instance integrated with Vault over OIDC login method.

    MTO integration with Vault and RHSSO provides a way for users to log in to Vault where they only have access to relevant tenant paths.

    Once both integrations are set up with IntegrationConfig CR, MTO links tenant users to specific client roles named after their tenant under Vault client in RHSSO.

    After that, MTO creates specific policies in Vault for its tenant users.

    Mapping of tenant roles to Vault is shown below

    Tenant Role Vault Path Vault Capabilities Owner, Editor (tenantName)/* Create, Read, Update, Delete, List Owner, Editor sys/mounts/(tenantName)/* Create, Read, Update, Delete, List Owner, Editor managed-addons/* Read, List Viewer (tenantName)/* Read

    A simple user login workflow is shown in the diagram below.

    "},{"location":"explanation/template.html","title":"Understanding and Utilizing Template","text":""},{"location":"explanation/template.html#creating-templates","title":"Creating Templates","text":"

    Anna wants to create a Template that she can use to initialize or share common resources across namespaces (e.g. PullSecrets).

    Anna can either create a template using manifests field, covering Kubernetes or custom resources.

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-pull-secret\nresources:\n  manifests:\n    - kind: Secret\n      apiVersion: v1\n      metadata:\n        name: docker-pull-secret\n      data:\n        .dockercfg: eyAKICAiaHR0cHM6IC8vaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsgImF1dGgiOiAiYzNSaGEyRjBaWEk2VjI5M1YyaGhkRUZIY21WaGRGQmhjM04zYjNKayJ9Cn0K\n      type: kubernetes.io/dockercfg\n

    Or by using Helm Charts

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: redis\nresources:\n  helm:\n    releaseName: redis\n    chart:\n      repository:\n        name: redis\n        repoUrl: https://charts.bitnami.com/bitnami\n        version: 0.0.15\n    values: |\n      redisPort: 6379\n

    She can also use resourceMapping field to copy over secrets and configmaps from one namespace to others.

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: resource-mapping\nresources:\n  resourceMappings:\n    secrets:\n      - name: docker-secret\n        namespace: bluesky-build\n    configMaps:\n      - name: tronador-configMap\n        namespace: stakater-tronador\n

    Note: Resource mapping can be used via TGI to map resources within tenant namespaces or to some other tenant's namespace. If used with TI, the resources will only be mapped if namespaces belong to same tenant.

    "},{"location":"explanation/template.html#using-templates-with-default-parameters","title":"Using Templates with Default Parameters","text":"
    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: namespace-parameterized-restrictions\nparameters:\n  # Name of the parameter\n  - name: DEFAULT_CPU_LIMIT\n    # The default value of the parameter\n    value: \"1\"\n  - name: DEFAULT_CPU_REQUESTS\n    value: \"0.5\"\n    # If a parameter is required the template instance will need to set it\n    # required: true\n    # Make sure only values are entered for this parameter\n    validation: \"^[0-9]*\\\\.?[0-9]+$\"\nresources:\n  manifests:\n    - apiVersion: v1\n      kind: LimitRange\n      metadata:\n        name: namespace-limit-range-${namespace}\n      spec:\n        limits:\n          - default:\n              cpu: \"${{DEFAULT_CPU_LIMIT}}\"\n            defaultRequest:\n              cpu: \"${{DEFAULT_CPU_REQUESTS}}\"\n            type: Container\n

    Parameters can be used with both manifests and helm charts

    "},{"location":"explanation/templated-metadata-values.html","title":"Templated values in Labels and Annotations","text":"

    Templated values are placeholders in your configuration that get replaced with actual data when the CR is processed. Below is a list of currently supported templated values, their descriptions, and where they can be used.

    "},{"location":"explanation/templated-metadata-values.html#supported-templated-values","title":"Supported templated values","text":"
    • \"{{ TENANT.USERNAME }}\"

      • Description: The username associated with users specified in Tenant under Owners and Editors.
      • Supported in CRs:
        • Tenant: Under sandboxMetadata.labels and sandboxMetadata.annotations.
        • IntegrationConfig: Under metadata.sandboxs.labels and metadata.sandboxs.annotations.
      • Example:
        annotation:\n    che.eclipse.org/username: \"{{ TENANT.USERNAME }}\" # double quotes are required\n
    "},{"location":"how-to-guides/configuring-multitenant-network-isolation.html","title":"Configuring Multi-Tenant Isolation with Network Policy Template","text":"

    Bill is a cluster admin who wants to configure network policies to provide multi-tenant network isolation.

    First, Bill creates a template for network policies:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: tenant-network-policy\nresources:\n  manifests:\n  - apiVersion: networking.k8s.io/v1\n    kind: NetworkPolicy\n    metadata:\n      name: allow-same-namespace\n    spec:\n      podSelector: {}\n      ingress:\n      - from:\n        - podSelector: {}\n  - apiVersion: networking.k8s.io/v1\n    kind: NetworkPolicy\n    metadata:\n      name: allow-from-openshift-monitoring\n    spec:\n      ingress:\n      - from:\n        - namespaceSelector:\n            matchLabels:\n              network.openshift.io/policy-group: monitoring\n      podSelector: {}\n      policyTypes:\n      - Ingress\n  - apiVersion: networking.k8s.io/v1\n    kind: NetworkPolicy\n    metadata:\n      name: allow-from-openshift-ingress\n    spec:\n      ingress:\n      - from:\n        - namespaceSelector:\n            matchLabels:\n              network.openshift.io/policy-group: ingress\n      podSelector: {}\n      policyTypes:\n      - Ingress\n

    Once the template has been created, Bill edits the IntegrationConfig to add unique label to all tenant projects:

    apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  metadata:\n    namespaces:\n      labels:\n        stakater.com/workload-monitoring: \"true\"\n        tenant-network-policy: \"true\"\n      annotations:\n        openshift.io/node-selector: node-role.kubernetes.io/worker=\n    sandbox:\n      labels:\n        stakater.com/kind: sandbox\n  privileged:\n    namespaces:\n      - default\n      - ^openshift.*\n      - ^kube.*\n    serviceAccounts:\n      - ^system:serviceaccount:openshift.*\n      - ^system:serviceaccount:kube.*\n

    Bill has added a new label tenant-network-policy: \"true\" in project section of IntegrationConfig, now MTO will add that label in all tenant projects.

    Finally, Bill creates a TemplateGroupInstance which will distribute the network policies using the newly added project label and template.

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: tenant-network-policy-group\nspec:\n  template: tenant-network-policy\n  selector:\n    matchLabels:\n      tenant-network-policy: \"true\"\n  sync: true\n

    MTO will now deploy the network policies mentioned in Template to all projects matching the label selector mentioned in the TemplateGroupInstance.

    "},{"location":"how-to-guides/copying-resources.html","title":"Propagate Secrets from Parent to Descendant namespaces","text":"

    Secrets like registry credentials often need to exist in multiple Namespaces, so that Pods within different namespaces can have access to those credentials in form of secrets.

    Manually creating secrets within different namespaces could lead to challenges, such as:

    • Someone will have to create secret either manually or via GitOps each time there is a new descendant namespace that needs the secret
    • If we update the parent secret, they will have to update the secret in all descendant namespaces
    • This could be time-consuming, and a small mistake while creating or updating the secret could lead to unnecessary debugging

    With the help of Multi-Tenant Operator's Template feature we can make this secret distribution experience easy.

    For example, to copy a Secret called registry which exists in the example to new Namespaces whenever they are created, we will first create a Template which will have reference of the registry secret.

    It will also push updates to the copied Secrets and keep the propagated secrets always sync and updated with parent namespaces.

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: registry-secret\nresources:\n  resourceMappings:\n    secrets:\n      - name: registry\n        namespace: example\n

    Now using this Template we can propagate registry secret to different namespaces that have some common set of labels.

    For example, will just add one label kind: registry and all namespaces with this label will get this secret.

    For propagating it on different namespaces dynamically will have to create another resource called TemplateGroupInstance. TemplateGroupInstance will have Template and matchLabel mapping as shown below:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: registry-secret-group-instance\nspec:\n  template: registry-secret\n  selector:\n    matchLabels:\n      kind: registry\n  sync: true\n

    After reconciliation, you will be able to see those secrets in namespaces having mentioned label.

    MTO will keep injecting this secret to the new namespaces created with that label.

    kubectl get secret registry-secret -n example-ns-1\nNAME             STATE    AGE\nregistry-secret    Active   3m\n\nkubectl get secret registry-secret -n example-ns-2\nNAME             STATE    AGE\nregistry-secret    Active   3m\n
    "},{"location":"how-to-guides/custom-metrics.html","title":"Custom Metrics Support","text":"

    Multi Tenant Operator now supports custom metrics for templates, template instances and template group instances. This feature allows users to monitor the usage of templates and template instances in their cluster.

    To enable custom metrics and view them in your OpenShift cluster, you need to follow the steps below:

    • Ensure that cluster monitoring is enabled in your cluster. You can check this by going to Observe -> Metrics in the OpenShift console.
    • Navigate to Administration -> Namespaces in the OpenShift console. Select the namespace where you have installed Multi Tenant Operator.
    • Add the following label to the namespace: openshift.io/cluster-monitoring=true. This will enable cluster monitoring for the namespace.
    • To ensure that the metrics are being scraped for the namespace, navigate to Observe -> Targets in the OpenShift console. You should see the namespace in the list of targets.
    • To view the custom metrics, navigate to Observe -> Metrics in the OpenShift console. You should see the custom metrics for templates, template instances and template group instances in the list of metrics.

    Details of metrics can be found at Metrics and Logs

    "},{"location":"how-to-guides/custom-roles.html","title":"Changing the default access level for tenant owners","text":"

    This feature allows the cluster admins to change the default roles assigned to Tenant owner, editor, viewer groups.

    For example, if Bill as the cluster admin wants to reduce the privileges that tenant owners have, so they cannot create or edit Roles or bind them. As an admin of an OpenShift cluster, Bill can do this by assigning the edit role to all tenant owners. This is easily achieved by modifying the IntegrationConfig:

    apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  accessControl:\n    rbac:\n      tenantRoles:\n        default:\n          owner:\n            clusterRoles:\n              - edit\n          editor:\n            clusterRoles:\n              - edit\n          viewer:\n            clusterRoles:\n              - view\n

    Once all namespaces reconcile, the old admin RoleBindings should get replaced with the edit ones for each tenant owner.

    "},{"location":"how-to-guides/custom-roles.html#giving-specific-permissions-to-some-tenants","title":"Giving specific permissions to some tenants","text":"

    Bill now wants the owners of the tenants bluesky and alpha to have admin permissions over their namespaces. Custom roles feature will allow Bill to do this, by modifying the IntegrationConfig like this:

    apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  accessControl:\n    rbac:\n      tenantRoles:\n        default:\n          owner:\n            clusterRoles:\n              - edit\n          editor:\n            clusterRoles:\n              - edit\n          viewer:\n            clusterRoles:\n              - view\n        custom:\n        - labelSelector:\n            matchExpressions:\n            - key: stakater.com/tenant\n              operator: In\n              values:\n                - alpha\n          owner:\n            clusterRoles:\n              - admin\n        - labelSelector:\n            matchExpressions:\n            - key: stakater.com/tenant\n              operator: In\n              values:\n                - bluesky\n          owner:\n            clusterRoles:\n              - admin\n

    New Bindings will be created for the Tenant owners of bluesky and alpha, corresponding to the admin Role. Bindings for editors and viewer will be inherited from the default roles. All other Tenant owners will have an edit Role bound to them within their namespaces

    "},{"location":"how-to-guides/deploying-private-helm-charts.html","title":"Deploying Private Helm Chart to Multiple Namespaces","text":"

    Multi Tenant Operator uses its helm functionality from Template and TemplateGroupInstance to deploy private and public charts to multiple namespaces.

    "},{"location":"how-to-guides/deploying-private-helm-charts.html#deploying-helm-chart-to-namespaces-via-templategroupinstances-from-oci-registry","title":"Deploying Helm Chart to Namespaces via TemplateGroupInstances from OCI Registry","text":"

    Bill, the cluster admin, wants to deploy a helm chart from OCI registry in namespaces where certain labels exists.

    First, Bill creates a template:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: chart-deploy\nresources:\n  helm:\n    releaseName: random-release\n    chart:\n      repository:\n        name: random-chart\n        repoUrl: 'oci://ghcr.io/stakater/charts/random-chart'\n        version: 0.0.15\n        password:\n          key: password\n          name: repo-user\n          namespace: shared-ns\n        username:\n          key: username\n          name: repo-user\n          namespace: shared-ns\n

    Once the template has been created, Bill makes a TemplateGroupInstance referring to the Template he wants to deploy with MatchLabels:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: chart-deploy\nspec:\n  selector:\n    matchExpressions:\n      - key: stakater.com/kind\n        operator: In\n        values:\n          - system\n  sync: true\n  template: chart-deploy\n

    Multi Tenant Operator will pick up the credentials from the mentioned namespace to pull the chart and apply it.

    Afterward, Bill can see that manifests in the chart have been successfully created in all label matching namespaces.

    "},{"location":"how-to-guides/deploying-private-helm-charts.html#deploying-helm-chart-to-namespaces-via-templategroupinstances-from-https-registry","title":"Deploying Helm Chart to Namespaces via TemplateGroupInstances from HTTPS Registry","text":"

    Bill, the cluster admin, wants to deploy a helm chart from HTTPS registry in namespaces where certain labels exists.

    First, Bill creates a template:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: chart-deploy\nresources:\n  helm:\n    releaseName: random-release\n    chart:\n      repository:\n        name: random-chart\n        repoUrl: 'nexus-helm-url/registry'\n        version: 0.0.15\n        password:\n          key: password\n          name: repo-user\n          namespace: shared-ns\n        username:\n          key: username\n          name: repo-user\n          namespace: shared-ns\n

    Once the template has been created, Bill makes a TemplateGroupInstance referring to the Template he wants to deploy with MatchLabels:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: chart-deploy\nspec:\n  selector:\n    matchExpressions:\n      - key: stakater.com/kind\n        operator: In\n        values:\n          - system\n  sync: true\n  template: chart-deploy\n

    Multi Tenant Operator will pick up the credentials from the mentioned namespace to pull the chart and apply it.

    Afterward, Bill can see that manifests in the chart have been successfully created in all label matching namespaces.

    "},{"location":"how-to-guides/deploying-templates.html","title":"Distributing Resources in Namespaces","text":"

    Multi Tenant Operator has two Custom Resources which can cover this need using the Template CR, depending upon the conditions and preference.

    1. TemplateGroupInstance
    2. TemplateInstance
    "},{"location":"how-to-guides/deploying-templates.html#deploying-template-to-namespaces-via-templategroupinstances","title":"Deploying Template to Namespaces via TemplateGroupInstances","text":"

    Bill, the cluster admin, wants to deploy a docker pull secret in namespaces where certain labels exists.

    First, Bill creates a template:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-secret\nresources:\n  manifests:\n    - kind: Secret\n      apiVersion: v1\n      metadata:\n        name: docker-pull-secret\n      data:\n        .dockercfg: eyAKICAiaHR0cHM6IC8vaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsgImF1dGgiOiAiYzNSaGEyRjBaWEk2VjI5M1YyaGhkRUZIY21WaGRGQmhjM04zYjNKayJ9Cn0K\n      type: kubernetes.io/dockercfg\n

    Once the template has been created, Bill makes a TemplateGroupInstance referring to the Template he wants to deploy with MatchLabels:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: docker-secret-group-instance\nspec:\n  template: docker-pull-secret\n  selector:\n    matchLabels:\n      kind: build\n  sync: true\n

    Afterward, Bill can see that secrets have been successfully created in all label matching namespaces.

    kubectl get secret docker-secret -n bluesky-anna-aurora-sandbox\nNAME             STATE    AGE\ndocker-secret    Active   3m\n\nkubectl get secret docker-secret -n alpha-dave-aurora-sandbox\nNAME             STATE    AGE\ndocker-secret    Active   2m\n

    TemplateGroupInstance can also target specific tenants or all tenant namespaces under a single YAML definition.

    "},{"location":"how-to-guides/deploying-templates.html#templategroupinstance-for-multiple-tenants","title":"TemplateGroupInstance for multiple Tenants","text":"

    It can be done by using the matchExpressions field, dividing the tenant label in key and values.

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: docker-secret-group-instance\nspec:\n  template: docker-pull-secret\n  selector:\n    matchExpressions:\n    - key: stakater.com/tenant\n      operator: In\n      values:\n        - alpha\n        - beta\n  sync: true\n
    "},{"location":"how-to-guides/deploying-templates.html#templategroupinstance-for-all-tenants","title":"TemplateGroupInstance for all Tenants","text":"

    This can also be done by using the matchExpressions field, using just the tenant label key stakater.com/tenant.

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: docker-secret-group-instance\nspec:\n  template: docker-pull-secret\n  selector:\n    matchExpressions:\n    - key: stakater.com/tenant\n      operator: Exists\n  sync: true\n
    "},{"location":"how-to-guides/deploying-templates.html#deploying-template-to-a-namespace-via-templateinstance","title":"Deploying Template to a Namespace via TemplateInstance","text":"

    Anna wants to deploy a docker pull secret in her namespace.

    First Anna asks Bill, the cluster admin, to create a template of the secret for her:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-pull-secret\nresources:\n  manifests:\n    - kind: Secret\n      apiVersion: v1\n      metadata:\n        name: docker-pull-secret\n      data:\n        .dockercfg: eyAKICAiaHR0cHM6IC8vaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsgImF1dGgiOiAiYzNSaGEyRjBaWEk2VjI5M1YyaGhkRUZIY21WaGRGQmhjM04zYjNKayJ9Cn0K\n      type: kubernetes.io/dockercfg\n

    Once the template has been created, Anna creates a TemplateInstance in her namespace referring to the Template she wants to deploy:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateInstance\nmetadata:\n  name: docker-pull-secret-instance\n  namespace: bluesky-anna-aurora-sandbox\nspec:\n  template: docker-pull-secret\n  sync: true\n

    Once this is created, Anna can see that the secret has been successfully applied.

    kubectl get secret docker-secret -n bluesky-anna-aurora-sandbox\nNAME                  STATE    AGE\ndocker-pull-secret    Active   3m\n
    "},{"location":"how-to-guides/deploying-templates.html#passing-parameters-to-template-via-templateinstance-templategroupinstance","title":"Passing Parameters to Template via TemplateInstance, TemplateGroupInstance","text":"

    Anna wants to deploy a LimitRange resource to certain namespaces.

    First Anna asks Bill, the cluster admin, to create template with parameters for LimitRange for her:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: namespace-parameterized-restrictions\nparameters:\n  # Name of the parameter\n  - name: DEFAULT_CPU_LIMIT\n    # The default value of the parameter\n    value: \"1\"\n  - name: DEFAULT_CPU_REQUESTS\n    value: \"0.5\"\n    # If a parameter is required the template instance will need to set it\n    # required: true\n    # Make sure only values are entered for this parameter\n    validation: \"^[0-9]*\\\\.?[0-9]+$\"\nresources:\n  manifests:\n    - apiVersion: v1\n      kind: LimitRange\n      metadata:\n        name: namespace-limit-range-${namespace}\n      spec:\n        limits:\n          - default:\n              cpu: \"${{DEFAULT_CPU_LIMIT}}\"\n            defaultRequest:\n              cpu: \"${{DEFAULT_CPU_REQUESTS}}\"\n            type: Container\n

    Afterward, Anna creates a TemplateInstance in her namespace referring to the Template she wants to deploy:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateInstance\nmetadata:\n  name: namespace-parameterized-restrictions-instance\n  namespace: bluesky-anna-aurora-sandbox\nspec:\n  template: namespace-parameterized-restrictions\n  sync: true\nparameters:\n  - name: DEFAULT_CPU_LIMIT\n    value: \"1.5\"\n  - name: DEFAULT_CPU_REQUESTS\n    value: \"1\"\n

    If she wants to distribute the same Template over multiple namespaces, she can use TemplateGroupInstance.

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: namespace-parameterized-restrictions-tgi\nspec:\n  template: namespace-parameterized-restrictions\n  sync: true\n  selector:\n    matchExpressions:\n    - key: stakater.com/tenant\n      operator: In\n      values:\n        - alpha\n        - beta\nparameters:\n  - name: DEFAULT_CPU_LIMIT\n    value: \"1.5\"\n  - name: DEFAULT_CPU_REQUESTS\n    value: \"1\"\n
    "},{"location":"how-to-guides/distributing-secrets-using-sealed-secret-template.html","title":"Distributing Secrets Using Sealed Secrets Template","text":"

    Bill is a cluster admin who wants to provide a mechanism for distributing secrets in multiple namespaces. For this, he wants to use Sealed Secrets as the solution by adding them to MTO Template CR

    First, Bill creates a Template in which Sealed Secret is mentioned:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: tenant-sealed-secret\nresources:\n  manifests:\n  - kind: SealedSecret\n    apiVersion: bitnami.com/v1alpha1\n    metadata:\n      name: mysecret\n    spec:\n      encryptedData:\n        .dockerconfigjson: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq.....\n      template:\n        type: kubernetes.io/dockerconfigjson\n        # this is an example of labels and annotations that will be added to the output secret\n        metadata:\n          labels:\n            \"jenkins.io/credentials-type\": usernamePassword\n          annotations:\n            \"jenkins.io/credentials-description\": credentials from Kubernetes\n

    Once the template has been created, Bill has to edit the Tenant to add unique label to namespaces in which the secret has to be deployed. For this, he can use the support for common and specific labels across namespaces.

    Bill has to specify a label on namespaces in which he needs the secret. He can add it to all namespaces inside a tenant or some specific namespaces depending on the use case.

    apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: false\n    withTenantPrefix:\n      - dev\n      - build\n      - prod\n    withoutTenantPrefix: []\n    metadata:\n      specific:\n        - namespaces:\n            - bluesky-test-namespace\n          labels:\n            distribute-image-pull-secret: true\n      common:\n        labels:\n          distribute-image-pull-secret: true\n

    Bill has added support for a new label distribute-image-pull-secret: true\" for tenant projects/namespaces, now MTO will add that label depending on the used field.

    Finally, Bill creates a TemplateGroupInstance which will deploy the sealed secrets using the newly created project label and template.

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: tenant-sealed-secret\nspec:\n  template: tenant-sealed-secret\n  selector:\n    matchLabels:\n      distribute-image-pull-secret: true\n  sync: true\n

    MTO will now deploy the sealed secrets mentioned in Template to namespaces which have the mentioned label. The rest of the work to deploy secret from a sealed secret has to be done by Sealed Secrets Controller.

    "},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html","title":"Enabling Multi-Tenancy in ArgoCD","text":""},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html#argocd-integration-in-multi-tenant-operator","title":"ArgoCD integration in Multi Tenant Operator","text":"

    With the Multi-Tenant Operator (MTO), cluster administrators can configure multi-tenancy within their cluster. The integration of ArgoCD with MTO allows for the configuration of multi-tenancy in ArgoCD applications and AppProjects.

    MTO can be configured to create AppProjects for each tenant. These AppProjects enable tenants to create ArgoCD Applications that can be synced to namespaces owned by them. Cluster admins can blacklist certain namespace resources and allow specific cluster-scoped resources as needed (see the NamespaceResourceBlacklist and ClusterResourceWhitelist sections in Integration Config docs and Tenant Custom Resource docs).

    Note that ArgoCD integration in MTO is optional.

    "},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html#default-argocd-configuration","title":"Default ArgoCD configuration","text":"

    We have set a default ArgoCD configuration in Multi Tenant Operator that fulfils the following use cases:

    • Tenants can only see their ArgoCD applications in the ArgoCD frontend.
    • Tenant 'Owners' and 'Editors' have full access to their ArgoCD applications.
    • Tenants in the 'Viewers' group have read-only access to their ArgoCD applications.
    • Tenants can sync all namespace-scoped resources, except those that are blacklisted.
    • Tenants can sync only cluster-scoped resources that are allow-listed.
    • Tenant 'Owners' can configure their own GitOps source repositories at the tenant level.
    • Cluster admins can prevent specific resources from syncing via ArgoCD.
    • Cluster admins have full access to all ArgoCD applications and AppProjects.
    • ArgoCD integration is on a per-tenant level; namespace-scoped applications are synced only to tenant namespaces.
    "},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html#creating-argocd-appprojects-for-your-tenant","title":"Creating ArgoCD AppProjects for your tenant","text":"

    To ensure each tenant has their own ArgoCD AppProjects, administrators must first specify the ArgoCD namespace in the IntegrationConfig:

    apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  ...\n  argocd:\n    namespace: openshift-operators\n  ...\n

    Administrators then create an Extension CR associated with the tenant:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Extensions\nmetadata:\n  name: extensions-sample\nspec:\n  tenantName: tenant-sample\n  argoCD:\n    onDeletePurgeAppProject: true\n    appProject:\n      sourceRepos:\n        - \"github.com/stakater/repo\"\n      clusterResourceWhitelist:\n        - group: \"\"\n          kind: \"Pod\"\n      namespaceResourceBlacklist:\n        - group: \"v1\"\n          kind: \"ConfigMap\"\n

    This creates an AppProject for the tenant:

    oc get AppProject -A\nNAMESPACE             NAME           AGE\nopenshift-operators   tenant-sample  5d15h\n

    Example of the created AppProject:

    apiVersion: argoproj.io/v1alpha1\nkind: AppProject\nmetadata:\n  name: tenant-sample\n  namespace: openshift-operators\nspec:\n  destinations:\n    - namespace: tenant-sample-build\n      server: \"https://kubernetes.default.svc\"\n    - namespace: tenant-sample-dev\n      server: \"https://kubernetes.default.svc\"\n    - namespace: tenant-sample-stage\n      server: \"https://kubernetes.default.svc\"\n  roles:\n    - description: >-\n        Role that gives full access to all resources inside the tenant's\n        namespace to the tenant owner groups\n      groups:\n        - saap-cluster-admins\n        - stakater-team\n        - tenant-sample-owner-group\n      name: tenant-sample-owner\n      policies:\n        - \"p, proj:tenant-sample:tenant-sample-owner, *, *, tenant-sample/*, allow\"\n    - description: >-\n        Role that gives edit access to all resources inside the tenant's\n        namespace to the tenant owner group\n      groups:\n        - saap-cluster-admins\n        - stakater-team\n        - tenant-sample-edit-group\n      name: tenant-sample-edit\n      policies:\n        - \"p, proj:tenant-sample:tenant-sample-edit, *, *, tenant-sample/*, allow\"\n    - description: >-\n        Role that gives view access to all resources inside the tenant's\n        namespace to the tenant owner group\n      groups:\n        - saap-cluster-admins\n        - stakater-team\n        - tenant-sample-view-group\n      name: tenant-sample-view\n      policies:\n        - \"p, proj:tenant-sample:tenant-sample-view, *, get, tenant-sample/*, allow\"\n  sourceRepos:\n    - \"https://github.com/stakater/gitops-config\"\n

    Users belonging to the tenant group will now see only applications created by them in the ArgoCD frontend:

    Note

    For ArgoCD Multi Tenancy to work properly, any default roles or policies attached to all users must be removed.

    "},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html#preventing-argocd-from-syncing-certain-namespaced-resources","title":"Preventing ArgoCD from Syncing Certain Namespaced Resources","text":"

    To prevent tenants from syncing ResourceQuota and LimitRange resources to their namespaces, administrators can specify these resources in the blacklist section of the ArgoCD configuration in the IntegrationConfig:

    apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  ...\n  integrations:\n    argocd:\n      namespace: openshift-operators\n      namespaceResourceBlacklist:\n        - group: \"\"\n          kind: ResourceQuota\n        - group: \"\"\n          kind: LimitRange\n  ...\n

    This configuration ensures these resources are not synced by ArgoCD if added to any tenant's project directory in GitOps. The AppProject will include the blacklisted resources:

    apiVersion: argoproj.io/v1alpha1\nkind: AppProject\nmetadata:\n  name: tenant-sample\n  namespace: openshift-operators\nspec:\n  ...\n  namespaceResourceBlacklist:\n    - group: ''\n      kind: ResourceQuota\n    - group: ''\n      kind: LimitRange\n  ...\n
    "},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html#allowing-argocd-to-sync-certain-cluster-wide-resources","title":"Allowing ArgoCD to Sync Certain Cluster-Wide Resources","text":"

    To allow tenants to sync the Environment cluster-scoped resource, administrators can specify this resource in the allow-list section of the ArgoCD configuration in the IntegrationConfig's spec:

    apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  ...\n  integrations:\n    argocd:\n      namespace: openshift-operators\n      clusterResourceWhitelist:\n        - group: \"\"\n          kind: Environment\n  ...\n

    This configuration ensures these resources are synced by ArgoCD if added to any tenant's project directory in GitOps. The AppProject will include the allow-listed resources:

    apiVersion: argoproj.io/v1alpha1\nkind: AppProject\nmetadata:\n  name: tenant-sample\n  namespace: openshift-operators\nspec:\n  ...\n  clusterResourceWhitelist:\n  - group: \"\"\n    kind: Environment\n  ...\n
    "},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html#overriding-namespaceresourceblacklist-andor-clusterresourcewhitelist-per-tenant","title":"Overriding NamespaceResourceBlacklist and/or ClusterResourceWhitelist Per Tenant","text":"

    To override the namespaceResourceBlacklist and/or clusterResourceWhitelist set via Integration Config for a specific tenant, administrators can specify these in the argoCD section of the Extension CR:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Extensions\nmetadata:\n  name: extensions-blue-sky\nspec:\n  tenantName: blue-sky\n  argoCD:\n    onDeletePurgeAppProject: true\n    appProject:\n      sourceRepos:\n        - \"github.com/stakater/repo\"\n      clusterResourceWhitelist:\n        - group: \"\"\n          kind: \"Pod\"\n      namespaceResourceBlacklist:\n        - group: \"v1\"\n          kind: \"ConfigMap\"\n

    This configuration allows for tailored settings for each tenant, ensuring flexibility and control over ArgoCD resources.

    "},{"location":"how-to-guides/enabling-multi-tenancy-vault.html","title":"Configuring Vault in IntegrationConfig","text":"

    Vault is used to secure, store and tightly control access to tokens, passwords, certificates, and encryption keys for protecting secrets and other sensitive data using a UI, CLI, or HTTP API.

    To enable Vault multi-tenancy, a role has to be created in Vault under Kubernetes authentication with the following permissions:

    path \"secret/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"sys/mounts\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"sys/mounts/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"managed-addons/*\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"auth/kubernetes/role/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"sys/auth\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"sys/policies/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"identity/group\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"identity/group-alias\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"identity/group/name/*\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"identity/group/id/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\n

    If Bill (the cluster admin) has Vault configured in his cluster, then he can take benefit from MTO's integration with Vault.

    MTO automatically creates Vault secret paths for tenants, where tenant members can securely save their secrets. It also authorizes tenant members to access these secrets via OIDC.

    Bill would first have to integrate Vault with MTO by adding the details in IntegrationConfig. For more details

    apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  integrations:\n    vault:\n      enabled: true\n      authMethod: kubernetes\n      accessInfo: \n        accessorPath: oidc/\n        address: https://vault.apps.prod.abcdefghi.kubeapp.cloud/\n        roleName: mto\n        secretRef:       \n          name: ''\n          namespace: ''\n      config: \n        ssoClient: vault\n

    Bill then creates a tenant for Anna and John:

    apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  accessControl:\n    owners:\n      users:\n      - anna@acme.org\n    viewers:\n      users:\n      - john@acme.org\n  quota: small\n  namespaces:\n    sandboxes:\n      enabled: false\n

    Now Bill goes to Vault and sees that a path for tenant has been made under the name bluesky/kv, confirming that Tenant members with the Owner or Edit roles now have access to the tenant's Vault path.

    Now if Anna sign's in to the Vault via OIDC, she can see her tenants path and secrets. Whereas if John sign's in to the Vault via OIDC, he can't see his tenants path or secrets as he doesn't have the access required to view them.

    For more details around enabling Kubernetes auth in Vault, visit here

    "},{"location":"how-to-guides/enabling-openshift-dev-workspace.html","title":"Enabling DevWorkspace for Tenant's sandbox in OpenShift","text":""},{"location":"how-to-guides/enabling-openshift-dev-workspace.html#devworkspaces-metadata-via-multi-tenant-operator","title":"DevWorkspaces metadata via Multi Tenant Operator","text":"

    DevWorkspaces require specific metadata on a namespace for it to work in it. With Multi Tenant Operator (MTO), you can create sandbox namespaces for users of a Tenant, and then add the required metadata automatically on all sandboxes.

    "},{"location":"how-to-guides/enabling-openshift-dev-workspace.html#required-metadata-for-enabling-devworkspace-on-sandbox","title":"Required metadata for enabling DevWorkspace on sandbox","text":"
      labels:\n    app.kubernetes.io/part-of: che.eclipse.org\n    app.kubernetes.io/component: workspaces-namespace\n  annotations:\n    che.eclipse.org/username: <username>\n
    "},{"location":"how-to-guides/enabling-openshift-dev-workspace.html#automate-sandbox-metadata-for-all-tenant-users-via-tenant-cr","title":"Automate sandbox metadata for all Tenant users via Tenant CR","text":"

    With Multi Tenant Operator (MTO), you can set sandboxMetadata like below to automate metadata for all sandboxes:

    apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@acme.org\n    editors:\n      users:\n        - erik@acme.org\n    viewers:\n      users:\n        - john@acme.org\n  namespaces:\n    sandboxes:\n      enabled: true\n      private: false\n    metadata:\n      sandbox:\n        labels:\n          app.kubernetes.io/part-of: che.eclipse.org\n          app.kubernetes.io/component: workspaces-namespace\n        annotations:\n          che.eclipse.org/username: \"{{ TENANT.USERNAME }}\"\n

    It will create sandbox namespaces and also apply the sandboxMetadata for owners and editors. Notice the template {{ TENANT.USERNAME }}, it will resolve the username as value of the corresponding annotation. For more info on templated value, see here

    "},{"location":"how-to-guides/enabling-openshift-dev-workspace.html#automate-sandbox-metadata-for-all-tenant-users-via-integrationconfig-cr","title":"Automate sandbox metadata for all Tenant users via IntegrationConfig CR","text":"

    You can also automate the metadata on all sandbox namespaces by using IntegrationConfig, notice metadata.sandboxes:

    apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  accessControl:\n    namespaceAccessPolicy:\n      deny:\n        privilegedNamespaces: {}\n    privileged:\n      namespaces:\n      - ^default$\n      - ^openshift.*\n      - ^kube.*\n      serviceAccounts:\n      - ^system:serviceaccount:openshift.*\n      - ^system:serviceaccount:kube.*\n      - ^system:serviceaccount:stakater-actions-runner-controller:actions-runner-controller-runner-deployment$\n    rbac:\n      tenantRoles:\n        default:\n          editor:\n            clusterRoles:\n            - edit\n          owner:\n            clusterRoles:\n            - admin\n          viewer:\n            clusterRoles:\n            - view\n  components:\n    console: false\n    ingress:\n      console: {}\n      gateway: {}\n      keycloak: {}\n    showback: false\n  integrations:\n    vault:\n      accessInfo:\n        accessorPath: \"\"\n        address: \"\"\n        roleName: \"\"\n        secretRef:\n          name: \"\"\n          namespace: \"\"\n      authMethod: kubernetes\n      config:\n        ssoClient: \"\"\n      enabled: false\n  metadata:\n    groups: {}\n    namespaces: {}\n    sandboxes:\n      labels:\n        app.kubernetes.io/part-of: che.eclipse.org\n        app.kubernetes.io/component: workspaces-namespace\n      annotations:\n        che.eclipse.org/username: \"{{ TENANT.USERNAME }}\"\n

    For more info on templated value \"{{ TENANT.USERNAME }}\", see here

    "},{"location":"how-to-guides/extend-default-roles.html","title":"Extending the default access level for tenant members","text":"

    Bill as the cluster admin wants to extend the default access for tenant members. As an admin of an OpenShift Cluster, Bill can extend the admin, edit, and view ClusterRole using aggregation. Bill will first create a ClusterRole with privileges to resources which Bill wants to extend. Bill will add the aggregation label to the newly created ClusterRole for extending the default ClusterRoles.

    kind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: extend-admin-role\n  labels:\n    rbac.authorization.k8s.io/aggregate-to-admin: 'true'\nrules:\n  - verbs:\n      - create\n      - update\n      - patch\n      - delete\n    apiGroups:\n      - user.openshift.io\n    resources:\n      - groups\n

    Note: You can learn more about aggregated-cluster-roles here

    "},{"location":"how-to-guides/graph-visualization.html","title":"Graph Visualization on MTO Console","text":"

    Effortlessly associate tenants with their respective resources using the enhanced graph feature on the MTO Console. This dynamic graph illustrates the relationships between tenants and the resources they create, encompassing both MTO's proprietary resources and native Kubernetes/OpenShift elements.

    Example Graph:

      graph LR;\n      A(alpha)-->B(dev);\n      A-->C(prod);\n      B-->D(limitrange);\n      B-->E(owner-rolebinding);\n      B-->F(editor-rolebinding);\n      B-->G(viewer-rolebinding);\n      C-->H(limitrange);\n      C-->I(owner-rolebinding);\n      C-->J(editor-rolebinding);\n      C-->K(viewer-rolebinding);

    Explore with an intuitive graph that showcases the relationships between tenants and their resources. The MTO Console's graph feature simplifies the understanding of complex structures, providing you with a visual representation of your tenant's organization.

    To view the graph of your tenant, follow the steps below:

    • Navigate to Tenants page on the MTO Console using the left navigation bar.
    • Click on View of the tenant for which you want to view the graph.
    • Click on Graph tab on the tenant details page.
    "},{"location":"how-to-guides/integrating-external-keycloak.html","title":"Integrating External Keycloak","text":"

    MTO Console uses Keycloak for authentication and authorization. By default, the MTO Console uses an internal Keycloak instance that is provisioned by the Multi Tenant Operator in its own namespace. However, you can also integrate an external Keycloak instance with the MTO Console.

    This guide will help you integrate an external Keycloak instance with the MTO Console.

    "},{"location":"how-to-guides/integrating-external-keycloak.html#prerequisites","title":"Prerequisites","text":"
    • An OpenShift cluster with Multi Tenant Operator installed.
    • An external Keycloak instance.
    "},{"location":"how-to-guides/integrating-external-keycloak.html#steps","title":"Steps","text":"

    Navigate to the Keycloak console.

    • Go to your realm.
    • Click on the Clients.
    • Click on the Create button to create a new client.

    Create a new client.

    • Fill in the Client ID, Client Name and Client Protocol fields.

    • Add Valid Redirect URIs and Web Origins for the client.

    Note: The Valid Redirect URIs and Web Origins should be the URL of the MTO Console.

    • Click on the Save button.
    "},{"location":"how-to-guides/integrating-external-keycloak.html#update-integration-config","title":"Update Integration Config","text":"
    • Update the IntegrationConfig CR with the following configuration.
    integrations: \n  keycloak:\n    realm: <realm>\n    address: <keycloak-address>\n    clientName: <client-name>\n
    • Now, the MTO Console will be integrated with the external Keycloak instance.
    "},{"location":"how-to-guides/keycloak.html","title":"Setting Up User Access in Keycloak for MTO Console","text":"

    This guide walks you through the process of adding new users in Keycloak and granting them access to Multi Tenant Operator (MTO) Console.

    "},{"location":"how-to-guides/keycloak.html#accessing-keycloak-console","title":"Accessing Keycloak Console","text":"
    • Log in to the OpenShift Console.
    • Go to the 'Routes' section within the 'multi-tenant-operator' namespace.
    • Click on the Keycloak console link provided in the Routes.
    • Login using the admin credentials (default: admin/admin).
    "},{"location":"how-to-guides/keycloak.html#adding-new-users-in-keycloak","title":"Adding new Users in Keycloak","text":"
    • In the Keycloak console, switch to the mto realm.
    • Go to the Users section in the mto realm.
    • Follow the prompts to add a new user.
    • Once you add a new user, here is how the Users section would look like
    "},{"location":"how-to-guides/keycloak.html#accessing-mto-console","title":"Accessing MTO Console","text":"
    • Go back to the OpenShift Console, navigate to the Routes section, and get the URL for the MTO Console.
    • Open the MTO Console URL and log in with the newly added user credentials.

    Now, at this point, a user will be authenticated to the MTO Console. But in order to get access to view any Tenant resources, the user will need to be part of a Tenant.

    "},{"location":"how-to-guides/keycloak.html#granting-access-to-tenant-resources","title":"Granting Access to Tenant Resources","text":"
    • Open Tenant CR: In the OpenShift cluster, locate and open the Tenant Custom Resource (CR) that you wish to give access to. You will see a YAML file similar to the following example:
    apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: arsenal\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - gabriel@arsenal.com\n      groups:\n        - arsenal\n    editors:\n      users:\n        - hakimi@arsenal.com\n    viewers:\n      users:\n        - neymar@arsenal.com\n
    • Edit Tenant CR: Add the newly created user's email to the appropriate section (owners, editors, viewers) in the Tenant CR. For example, if you have created a user john@arsenal.com and wish to add them as an editor, the edited section would look like this:
    editors:\n  users:\n    - gabriel@arsenal.com\n    - benzema@arsenal.com\n
    • Save Changes: Save and apply the changes to the Tenant CR.
    "},{"location":"how-to-guides/keycloak.html#verifying-access","title":"Verifying Access","text":"

    Once the above steps are completed, you should be able to access the MTO Console now and see alpha Tenant's details along with all the other resources such as namespaces and templates that John has access to.

    "},{"location":"how-to-guides/mattermost.html","title":"Creating Mattermost Teams for your tenant","text":""},{"location":"how-to-guides/mattermost.html#requirements","title":"Requirements","text":"

    MTO-Mattermost-Integration-Operator

    Please contact stakater to install the Mattermost integration operator before following the below-mentioned steps.

    "},{"location":"how-to-guides/mattermost.html#steps-to-enable-integration","title":"Steps to enable integration","text":"

    Bill wants some tenants to also have their own Mattermost Teams. To make sure this happens correctly, Bill will first add the stakater.com/mattermost: true label to the tenants. The label will enable the mto-mattermost-integration-operator to create and manage Mattermost Teams based on Tenants.

    apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: sigma\n  labels:\n    stakater.com/mattermost: 'true'\nspec:\n  quota: medium\n  accessControl:\n    owners:\n      users:\n        - user\n    editors:\n      users:\n        - user1\n  namespaces:\n    sandboxes:\n      enabled: false\n    withTenantPrefix:\n      - dev\n      - build\n      - prod\n

    Now user can log In to Mattermost to see their Team and relevant channels associated with it.

    The name of the Team is similar to the Tenant name. Notification channels are pre-configured for every team, and can be modified.

    "},{"location":"how-to-guides/resource-sync-by-tgi.html","title":"Sync Resources Deployed by TemplateGroupInstance","text":"

    The TemplateGroupInstance CR provides two types of resource sync for the resources mentioned in Template

    For the given example, let's consider we want to apply the following template

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-secret\nresources:\n  manifests:\n\n    - kind: Secret\n      apiVersion: v1\n      metadata:\n        name: docker-pull-secret\n      data:\n        .dockercfg: eyAKICAiaHR0cHM6IC8vaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsgImF1dGgiOiAiYzNSaGEyRjBaWEk2VjI5M1YyaGhkRUZIY21WaGRGQmhjM04zYjNKayJ9Cn0K\n      type: kubernetes.io/dockercfg\n\n    - apiVersion: v1\n      kind: ServiceAccount\n      metadata:\n        name: example-automated-thing\n      secrets:\n        - name: example-automated-thing-token-zyxwv\n

    And the following TemplateGroupInstance is used to deploy these resources to namespaces having label kind: build

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: docker-secret-group-instance\nspec:\n  template: docker-secret\n  selector:\n    matchLabels:\n      kind: build\n  sync: true\n

    As we can see, in our TGI, we have a field spec.sync which is set to true. This will update the resources on two conditions:

    • The Template CR is updated
    • The TemplateGroupInstance CR is reconciled/updated

    • If, for any reason, the underlying resource gets updated or deleted, TemplateGroupInstance CR will try to revert it back to the state mentioned in the Template CR.

    Note

    Updates to ServiceAccounts are ignored by both, reconciler and informers, in an attempt to avoid conflict between the TGI controller and Kube Controller Manager. ServiceAccounts are only reverted in case of unexpected deletions when sync is true.

    "},{"location":"how-to-guides/resource-sync-by-tgi.html#ignore-resources-updates-on-resources","title":"Ignore Resources Updates on Resources","text":"

    If the resources mentioned in Template CR conflict with another controller/operator, and you want TemplateGroupInstance to not actively revert the resource updates, you can add the following label to the conflicting resource multi-tenant-operator/ignore-resource-updates: \"\".

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-secret\nresources:\n  manifests:\n\n    - kind: Secret\n      apiVersion: v1\n      metadata:\n        name: docker-pull-secret\n      data:\n        .dockercfg: eyAKICAiaHR0cHM6IC8vaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsgImF1dGgiOiAiYzNSaGEyRjBaWEk2VjI5M1YyaGhkRUZIY21WaGRGQmhjM04zYjNKayJ9Cn0K\n      type: kubernetes.io/dockercfg\n\n    - apiVersion: v1\n      kind: ServiceAccount\n      metadata:\n        name: example-automated-thing\n        labels:\n          multi-tenant-operator/ignore-resource-updates: \"\"\n      secrets:\n        - name: example-automated-thing-token-zyxwv\n

    Note

    However, this label will not stop Multi Tenant Operator from updating the resource on following conditions: - Template gets updated - TemplateGroupInstance gets updated - Resource gets deleted

    If you don't want to sync the resources in any case, you can disable sync via sync: false in TemplateGroupInstance spec.

    "},{"location":"how-to-guides/offboarding/uninstalling.html","title":"Uninstall via OperatorHub UI on OpenShift","text":"

    You can uninstall MTO by following these steps:

    • Decide on whether you want to retain tenant namespaces and ArgoCD AppProjects or not. If yes, please set spec.onDelete.cleanNamespaces to false for all those tenants whose namespaces you want to retain, and spec.onDelete.cleanAppProject to false for all those tenants whose AppProject you want to retain. For more details check out onDelete

    • In case you have enabled console, you will have to disable it first by navigating to Search -> IntegrationConfig -> tenant-operator-config and set spec.provision.console and spec.provision.showback to false.

    • Remove IntegrationConfig CR from the cluster by navigating to Search -> IntegrationConfig -> tenant-operator-config and select Delete from actions dropdown.

    • After making the required changes open OpenShift console and click on Operators, followed by Installed Operators from the side menu

    • Now click on uninstall and confirm uninstall.

    • Now the operator has been uninstalled.

    • Optional: you can also manually remove MTO's CRDs and its resources from the cluster.

    "},{"location":"how-to-guides/offboarding/uninstalling.html#notes","title":"Notes","text":"
    • For more details on how to use MTO please refer Tenant's tutorial.
    • For more details on how to extend your MTO manager ClusterRole please refer extend-default-clusterroles.
    "},{"location":"installation/openshift.html","title":"On OpenShift","text":"

    This document contains instructions on installing, uninstalling and configuring Multi Tenant Operator using OpenShift MarketPlace.

    1. OpenShift OperatorHub UI

    2. CLI/GitOps

    3. Enabling Console

    4. License configuration

    5. Uninstall

    "},{"location":"installation/openshift.html#requirements","title":"Requirements","text":"
    • An OpenShift cluster [v4.8 - v4.13]
    "},{"location":"installation/openshift.html#installing-via-operatorhub-ui","title":"Installing via OperatorHub UI","text":"
    • After opening OpenShift console click on Operators, followed by OperatorHub from the side menu
    • Now search for Multi Tenant Operator and then click on Multi Tenant Operator tile
    • Click on the install button
    • Select Updated channel. Select multi-tenant-operator to install the operator in multi-tenant-operator namespace from Installed Namespace dropdown menu. After configuring Update approval click on the install button.

    Note: Use stable channel for seamless upgrades. For Production Environment prefer Manual approval and use Automatic for Development Environment

    • Wait for the operator to be installed

    • Once successfully installed, MTO will be ready to enforce multi-tenancy in your cluster

    Note: MTO will be installed in multi-tenant-operator namespace.

    "},{"location":"installation/openshift.html#installing-via-cli-or-gitops","title":"Installing via CLI OR GitOps","text":"
    • Create namespace multi-tenant-operator
    oc create namespace multi-tenant-operator\nnamespace/multi-tenant-operator created\n
    • Create an OperatorGroup YAML for MTO and apply it in multi-tenant-operator namespace.
    oc create -f - << EOF\napiVersion: operators.coreos.com/v1\nkind: OperatorGroup\nmetadata:\n  name: tenant-operator\n  namespace: multi-tenant-operator\nEOF\noperatorgroup.operators.coreos.com/tenant-operator created\n
    • Create a subscription YAML for MTO and apply it in multi-tenant-operator namespace. To enable console set .spec.config.env[].ENABLE_CONSOLE to true. This will create a route resource, which can be used to access the Multi-Tenant-Operator console.
    oc create -f - << EOF\napiVersion: operators.coreos.com/v1alpha1\nkind: Subscription\nmetadata:\n  name: tenant-operator\n  namespace: multi-tenant-operator\nspec:\n  channel: stable\n  installPlanApproval: Automatic\n  name: tenant-operator\n  source: certified-operators\n  sourceNamespace: openshift-marketplace\n  startingCSV: tenant-operator.v0.10.0\nEOF\nsubscription.operators.coreos.com/tenant-operator created\n

    Note: To bring MTO via GitOps, add the above files in GitOps repository.

    • After creating the subscription custom resource open OpenShift console and click on Operators, followed by Installed Operators from the side menu

    • Wait for the installation to complete

    • Once the installation is complete click on Workloads, followed by Pods from the side menu and select multi-tenant-operator project

    • Once pods are up and running, MTO will be ready to enforce multi-tenancy in your cluster

    For more details and configurations check out IntegrationConfig.

    "},{"location":"installation/openshift.html#enabling-console","title":"Enabling Console","text":"

    To enable console GUI for MTO, go to Search -> IntegrationConfig -> tenant-operator-config and make sure the following fields are set to true:

    spec:\n  components:\n    console: true\n    showback: true\n

    Note: If your InstallPlan approval is set to Manual then you will have to manually approve the InstallPlan for MTO console components to be installed.

    "},{"location":"installation/openshift.html#manual-approval","title":"Manual Approval","text":"
    • Open OpenShift console and click on Operators, followed by Installed Operators from the side menu.
    • Now click on Upgrade available in front of mto-opencost or mto-prometheus.
    • Now click on Preview InstallPlan on top.
    • Now click on Approve button.
    • Now the InstallPlan will be approved, and MTO console components will be installed.
    "},{"location":"installation/openshift.html#license-configuration","title":"License Configuration","text":"

    We offer a free license with installation, and you can create max 2 Tenants with it.

    We offer a paid license as well. You need to have a configmap license created in MTO's namespace (multi-tenant-operator). To get this configmap, you can contact sales@stakater.com. It would look like this:

    apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: license\n  namespace: multi-tenant-operator\ndata:\n  payload.json: |\n    {\n        \"metaData\": {\n            \"tier\" : \"paid\",\n            \"company\": \"<company name here>\"\n        }\n    }\n  signature.base64.txt: <base64 signature here.>\n
    "},{"location":"installation/openshift.html#uninstall-via-operatorhub-ui","title":"Uninstall via OperatorHub UI","text":"

    You can uninstall MTO by following these steps:

    • Decide on whether you want to retain tenant namespaces and ArgoCD AppProjects or not. If yes, please set spec.onDelete.cleanNamespaces to false for all those tenants whose namespaces you want to retain, and spec.onDelete.cleanAppProject to false for all those tenants whose AppProject you want to retain. For more details check out onDelete

    • After making the required changes open OpenShift console and click on Operators, followed by Installed Operators from the side menu

    • Now click on uninstall and confirm uninstall.

    • Now the operator has been uninstalled.

    • Optional: you can also manually remove MTO's CRDs and its resources from the cluster.

    "},{"location":"installation/openshift.html#notes","title":"Notes","text":"
    • For more details on how to use MTO please refer Tenant tutorial.
    • For more details on how to extend your MTO manager ClusterRole please refer extend-default-clusterroles.
    "},{"location":"tutorials/distributing-resources/copying-resources.html","title":"Copying Secrets and Configmaps across Tenant Namespaces via TGI","text":"

    Bill is a cluster admin who wants to map a docker-pull-secret, present in a build namespace, in tenant namespaces where certain labels exists.

    First, Bill creates a template:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-pull-secret\nresources:\n  resourceMappings:\n    secrets:\n      - name: docker-pull-secret\n        namespace: build\n

    Once the template has been created, Bill makes a TemplateGroupInstance referring to the Template he wants to deploy with MatchLabels:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: docker-secret-group-instance\nspec:\n  template: docker-pull-secret\n  selector:\n    matchLabels:\n      kind: build\n  sync: true\n

    Afterward, Bill can see that secrets has been successfully mapped in all matching namespaces.

    kubectl get secret docker-pull-secret -n bluesky-anna-aurora-sandbox\nNAME             STATE    AGE\ndocker-pull-secret    Active   3m\n\nkubectl get secret docker-pull-secret -n alpha-dave-aurora-sandbox\nNAME             STATE    AGE\ndocker-pull-secret    Active   3m\n
    "},{"location":"tutorials/distributing-resources/copying-resources.html#mapping-resources-within-tenant-namespaces-via-ti","title":"Mapping Resources within Tenant Namespaces via TI","text":"

    Anna is a tenant owner who wants to map a docker-pull-secret, present in bluseky-build namespace, to bluesky-anna-aurora-sandbox namespace.

    First, Bill creates a template:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-pull-secret\nresources:\n  resourceMappings:\n    secrets:\n      - name: docker-pull-secret\n        namespace: bluesky-build\n

    Once the template has been created, Anna creates a TemplateInstance in bluesky-anna-aurora-sandbox namespace, referring to the Template.

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateInstance\nmetadata:\n  name: docker-secret-instance\n  namespace: bluesky-anna-aurora-sandbox\nspec:\n  template: docker-pull-secret\n  sync: true\n

    Afterward, Bill can see that secrets has been successfully mapped in all matching namespaces.

    kubectl get secret docker-pull-secret -n bluesky-anna-aurora-sandbox\nNAME             STATE    AGE\ndocker-pull-secret    Active   3m\n
    "},{"location":"tutorials/distributing-resources/distributing-manifests.html","title":"Distributing Resources in Namespaces","text":"

    Multi Tenant Operator has two Custom Resources which can cover this need using the Template CR, depending upon the conditions and preference.

    1. TemplateGroupInstance
    2. TemplateInstance
    "},{"location":"tutorials/distributing-resources/distributing-manifests.html#deploying-template-to-namespaces-via-templategroupinstances","title":"Deploying Template to Namespaces via TemplateGroupInstances","text":"

    Bill, the cluster admin, wants to deploy a docker pull secret in namespaces where certain labels exists.

    First, Bill creates a template:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-secret\nresources:\n  manifests:\n    - kind: Secret\n      apiVersion: v1\n      metadata:\n        name: docker-pull-secret\n      data:\n        .dockercfg: eyAKICAiaHR0cHM6IC8vaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsgImF1dGgiOiAiYzNSaGEyRjBaWEk2VjI5M1YyaGhkRUZIY21WaGRGQmhjM04zYjNKayJ9Cn0K\n      type: kubernetes.io/dockercfg\n

    Once the template has been created, Bill makes a TemplateGroupInstance referring to the Template he wants to deploy with template field, and the namespaces where resources are needed, using selector field:

    apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: docker-secret-group-instance\nspec:\n  template: docker-pull-secret\n  selector:\n    matchExpressions:\n    - key: kind\n      operator: In\n      values:\n        - build\n  sync: true\n

    Afterward, Bill can see that secrets have been successfully created in all label matching namespaces.

    kubectl get secret docker-secret -n bluesky-anna-aurora-sandbox\nNAME             STATE    AGE\ndocker-secret    Active   3m\n\nkubectl get secret docker-secret -n alpha-dave-aurora-sandbox\nNAME             STATE    AGE\ndocker-secret    Active   2m\n

    TemplateGroupInstance can also target specific tenants or all tenant namespaces under a single YAML definition.

    "},{"location":"tutorials/tenant/assigning-metadata.html","title":"Assigning Metadata in Tenant Custom Resources","text":"

    In the v1beta3 version of the Tenant Custom Resource (CR), metadata assignment has been refined to offer granular control over labels and annotations across different namespaces associated with a tenant. This functionality enables precise and flexible management of metadata, catering to both general and specific needs.

    "},{"location":"tutorials/tenant/assigning-metadata.html#distributing-common-labels-and-annotations","title":"Distributing Common Labels and Annotations","text":"

    To apply common labels and annotations across all namespaces within a tenant, the namespaces.metadata.common field in the Tenant CR is utilized. This approach ensures that essential metadata is uniformly present across all namespaces, supporting consistent identification, management, and policy enforcement.

    kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    withTenantPrefix:\n      - dev\n      - build\n      - prod\n    metadata:\n      common:\n        labels:\n          app.kubernetes.io/managed-by: tenant-operator\n          app.kubernetes.io/part-of: tenant-alpha\n        annotations:\n          openshift.io/node-selector: node-role.kubernetes.io/infra=\nEOF\n

    By configuring the namespaces.metadata.common field as shown, all namespaces within the tenant will inherit the specified labels and annotations.

    "},{"location":"tutorials/tenant/assigning-metadata.html#distributing-specific-labels-and-annotations","title":"Distributing Specific Labels and Annotations","text":"

    For scenarios requiring targeted application of labels and annotations to specific namespaces, the Tenant CR's namespaces.metadata.specific field is designed. This feature enables the assignment of unique metadata to designated namespaces, accommodating specialized configurations and requirements.

    kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    withTenantPrefix:\n      - dev\n      - build\n      - prod\n    metadata:\n      specific:\n        - namespaces:\n            - bluesky-dev\n          labels:\n            app.kubernetes.io/is-sandbox: \"true\"\n          annotations:\n            openshift.io/node-selector: node-role.kubernetes.io/worker=\nEOF\n

    This configuration directs the specific labels and annotations solely to the enumerated namespaces, enabling distinct settings for particular environments.

    "},{"location":"tutorials/tenant/assigning-metadata.html#assigning-metadata-to-sandbox-namespaces","title":"Assigning Metadata to Sandbox Namespaces","text":"

    To specifically address sandbox namespaces within the tenant, the namespaces.metadata.sandbox property of the Tenant CR is employed. This section allows for the distinct management of sandbox namespaces, enhancing security and differentiation in development or testing environments.

    apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: true\n      private: true\n    metadata:\n      sandbox:\n        labels:\n          app.kubernetes.io/part-of: che.eclipse.org\n        annotations:\n          che.eclipse.org/username: \"{{ TENANT.USERNAME }}\" # templated placeholder\n

    This setup ensures that all sandbox namespaces receive the designated metadata, with support for templated values, such as {{ TENANT.USERNAME }}, allowing dynamic customization based on the tenant or user context.

    These enhancements in metadata management within the v1beta3 version of the Tenant CR provide comprehensive and flexible tools for labeling and annotating namespaces, supporting a wide range of organizational, security, and operational objectives.

    "},{"location":"tutorials/tenant/create-sandbox.html","title":"Create Sandbox Namespaces for Tenant Users","text":"

    Sandbox namespaces offer a personal development and testing space for users within a tenant. This guide covers how to enable and configure sandbox namespaces for tenant users, along with setting privacy and applying metadata specifically for these sandboxes.

    "},{"location":"tutorials/tenant/create-sandbox.html#enabling-sandbox-namespaces","title":"Enabling Sandbox Namespaces","text":"

    Bill has assigned the ownership of the tenant bluesky to Anna and Anthony. To provide them with their sandbox namespaces, he must enable the sandbox functionality in the tenant's configuration.

    To enable sandbox namespaces, Bill updates the Tenant Custom Resource (CR) with sandboxes.enabled: true:

    kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: true\nEOF\n

    This configuration automatically generates sandbox namespaces for Anna, Anthony, and even John (as an editor) with the naming convention <tenantName>-<userName>-sandbox.

    kubectl get namespaces\nNAME                             STATUS   AGE\nbluesky-anna-aurora-sandbox      Active   5d5h\nbluesky-anthony-aurora-sandbox   Active   5d5h\nbluesky-john-aurora-sandbox      Active   5d5h\n
    "},{"location":"tutorials/tenant/create-sandbox.html#creating-private-sandboxes","title":"Creating Private Sandboxes","text":"

    To address privacy concerns where users require their sandbox namespaces to be visible only to themselves, Bill can set the sandboxes.private: true in the Tenant CR:

    kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: true\n      private: true\nEOF\n

    With private: true, each sandbox namespace is accessible and visible only to its designated user, enhancing privacy and security.

    With the above configuration Anna and Anthony will now have new sandboxes created

    kubectl get namespaces\nNAME                             STATUS   AGE\nbluesky-anna-aurora-sandbox      Active   5d5h\nbluesky-anthony-aurora-sandbox   Active   5d5h\nbluesky-john-aurora-sandbox      Active   5d5h\n

    However, from the perspective of Anna, only their sandbox will be visible

    kubectl get namespaces\nNAME                             STATUS   AGE\nbluesky-anna-aurora-sandbox      Active   5d5h\n
    "},{"location":"tutorials/tenant/create-sandbox.html#applying-metadata-to-sandbox-namespaces","title":"Applying Metadata to Sandbox Namespaces","text":"

    For uniformity or to apply specific policies, Bill might need to add common metadata, such as labels or annotations, to all sandbox namespaces. This is achievable through the namespaces.metadata.sandbox configuration:

    kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: true\n      private: true\n    metadata:\n      sandbox:\n        labels:\n          app.kubernetes.io/part-of: che.eclipse.org\n        annotations:\n          che.eclipse.org/username: \"{{ TENANT.USERNAME }}\"\nEOF\n

    The templated annotation \"{{ TENANT.USERNAME }}\" dynamically inserts the username of the sandbox owner, personalizing the sandbox environment. This capability is particularly useful for integrating with other systems or applications that might utilize this metadata for configuration or access control.

    Through the examples demonstrated, Bill can efficiently manage sandbox namespaces for tenant users, ensuring they have the necessary resources for development and testing while maintaining privacy and organizational policies.

    "},{"location":"tutorials/tenant/create-tenant.html","title":"Creating a Tenant","text":"

    Bill, a cluster admin, has been tasked by the CTO of Nordmart to set up a new tenant for Anna's team. Following the request, Bill proceeds to create a new tenant named bluesky in the Kubernetes cluster.

    "},{"location":"tutorials/tenant/create-tenant.html#setting-up-the-tenant","title":"Setting Up the Tenant","text":"

    To establish the tenant, Bill crafts a Tenant Custom Resource (CR) with the necessary specifications:

    kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: false\nEOF\n

    In this configuration, Bill specifies anna@aurora.org as the owner, giving her full administrative rights over the tenant. The editor role is assigned to john@aurora.org and the group alpha, providing them with editing capabilities within the tenant's scope.

    "},{"location":"tutorials/tenant/create-tenant.html#verifying-the-tenant-creation","title":"Verifying the Tenant Creation","text":"

    After creating the tenant, Bill checks its status to confirm it's active and operational:

    kubectl get tenants.tenantoperator.stakater.com bluesky\nNAME       STATE    AGE\nbluesky    Active   3m\n

    This output indicates that the tenant bluesky is successfully created and in an active state.

    "},{"location":"tutorials/tenant/create-tenant.html#checking-user-permissions","title":"Checking User Permissions","text":"

    To ensure the roles and permissions are correctly assigned, Anna logs into the cluster to verify her capabilities:

    Namespace Creation:

    kubectl auth can-i create namespaces\nyes\n

    Anna is confirmed to have the ability to create namespaces within the tenant's scope.

    Cluster Resources Access:

    kubectl auth can-i get namespaces\nno\n\nkubectl auth can-i get persistentvolumes\nno\n

    As expected, Anna does not have access to broader cluster resources outside the tenant's confines.

    Tenant Resource Access:

    kubectl auth can-i get tenants.tenantoperator.stakater.com\nno\n

    Access to the Tenant resource itself is also restricted, aligning with the principle of least privilege.

    "},{"location":"tutorials/tenant/create-tenant.html#adding-multiple-owners-to-a-tenant","title":"Adding Multiple Owners to a Tenant","text":"

    Later, if there's a need to grant administrative privileges to another user, such as Anthony, Bill can easily update the tenant's configuration to include multiple owners:

    kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: false\nEOF\n

    With this update, both Anna and Anthony can administer the tenant bluesky, including the creation of namespaces:

    kubectl auth can-i create namespaces\nyes\n

    This flexible approach allows Bill to manage tenant access control efficiently, ensuring that the team's operational needs are met while maintaining security and governance standards.

    "},{"location":"tutorials/tenant/creating-namespaces.html","title":"Creating Namespaces through Tenant Custom Resource","text":"

    Bill, tasked with structuring namespaces for different environments within a tenant, utilizes the Tenant Custom Resource (CR) to streamline this process efficiently. Here's how Bill can orchestrate the creation of dev, build, and production environments for the tenant members directly through the Tenant CR.

    "},{"location":"tutorials/tenant/creating-namespaces.html#strategy-for-namespace-creation","title":"Strategy for Namespace Creation","text":"

    To facilitate the environment setup, Bill decides to categorize the namespaces based on their association with the tenant's name. He opts to use the namespaces.withTenantPrefix field for namespaces that should carry the tenant name as a prefix, enhancing clarity and organization. For namespaces that do not require a tenant name prefix, Bill employs the namespaces.withoutTenantPrefix field.

    Here's how Bill configures the Tenant CR to create these namespaces:

    kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    withTenantPrefix:\n      - dev\n      - build\n    withoutTenantPrefix:\n      - prod\nEOF\n

    This configuration ensures the creation of the desired namespaces, directly correlating them with the bluesky tenant.

    Upon applying the above configuration, Bill and the tenant members observe the creation of the following namespaces:

    kubectl get namespaces\nNAME             STATUS   AGE\nbluesky-dev      Active   5m\nbluesky-build    Active   5m\nprod             Active   5m\n

    Anna, as a tenant owner, gains the capability to further customize or create new namespaces within her tenant's scope. For example, creating a bluesky-production namespace with the necessary tenant label:

    apiVersion: v1\nkind: Namespace\nmetadata:\n  name: bluesky-production\n  labels:\n    stakater.com/tenant: bluesky\n

    \u26a0\ufe0f It's crucial for Anna to include the tenant label tenantoperator.stakater.com/tenant: bluesky to ensure the namespace is recognized as part of the bluesky tenant. Failure to do so, or if Anna is not associated with the bluesky tenant, will result in Multi Tenant Operator (MTO) denying the namespace creation.

    Following the creation, the MTO dynamically assigns roles to Anna and other tenant members according to their designated user types, ensuring proper access control and operational capabilities within these namespaces.

    "},{"location":"tutorials/tenant/creating-namespaces.html#incorporating-existing-namespaces-into-the-tenant-via-argocd","title":"Incorporating Existing Namespaces into the Tenant via ArgoCD","text":"

    For teams practicing GitOps, existing namespaces can be seamlessly integrated into the Tenant structure by appending the tenant label to the namespace's manifest within the GitOps repository. This approach allows for efficient, automated management of namespace affiliations and access controls, ensuring a cohesive tenant ecosystem.

    "},{"location":"tutorials/tenant/creating-namespaces.html#add-existing-namespaces-to-tenant-via-gitops","title":"Add Existing Namespaces to Tenant via GitOps","text":"

    Using GitOps as your preferred development workflow, you can add existing namespaces for your tenants by including the tenant label.

    To add an existing namespace to your tenant via GitOps:

    1. Migrate the namespace resource to the GitOps-monitored repository
    2. Amend the namespace manifest to include the tenant label: tenantoperator.stakater.com/tenant: .
    3. Synchronize the GitOps repository with the cluster to propagate the changes
    4. Validate that the tenant users now have appropriate access to the integrated namespace
    5. "},{"location":"tutorials/tenant/creating-namespaces.html#removing-namespaces-via-gitops","title":"Removing Namespaces via GitOps","text":"

      To disassociate or remove namespaces from the cluster through GitOps, the namespace configuration should be eliminated from the GitOps repository. Additionally, detaching the namespace from any ArgoCD-managed applications by removing the app.kubernetes.io/instance label ensures a clean removal without residual dependencies.

      Synchronizing the repository post-removal finalizes the deletion process, effectively managing the lifecycle of namespaces within a tenant-operated Kubernetes environment.

      "},{"location":"tutorials/tenant/deleting-tenant.html","title":"Deleting a Tenant While Preserving Resources","text":"

      When managing tenant lifecycles within Kubernetes, certain scenarios require the deletion of a tenant without removing associated namespaces or ArgoCD AppProjects. This ensures that resources and configurations tied to the tenant remain intact for archival or transition purposes.

      "},{"location":"tutorials/tenant/deleting-tenant.html#configuration-for-retaining-resources","title":"Configuration for Retaining Resources","text":"

      Bill decides to decommission the bluesky tenant but needs to preserve all related namespaces for continuity. To achieve this, he adjusts the Tenant Custom Resource (CR) to prevent the automatic cleanup of these resources upon tenant deletion.

      kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n  namespaces:\n    sandboxes:\n      enabled: true\n    withTenantPrefix:\n      - dev\n      - build\n      - prod\n    onDeletePurgeNamespaces: false\nEOF\n

      With the onDeletePurgeNamespaces fields set to false, Bill ensures that the deletion of the bluesky tenant does not trigger the removal of its namespaces. This setup is crucial for cases where the retention of environment setups and deployments is necessary post-tenant deletion.

      "},{"location":"tutorials/tenant/deleting-tenant.html#default-behavior","title":"Default Behavior","text":"

      It's important to note the default behavior of the Tenant Operator regarding resource cleanup:

      Namespaces: By default, onDeletePurgeNamespaces is set to false, implying that namespaces are not automatically deleted with the tenant unless explicitly configured.

      "},{"location":"tutorials/tenant/deleting-tenant.html#deleting-the-tenant","title":"Deleting the Tenant","text":"

      Once the Tenant CR is configured as desired, Bill can proceed to delete the bluesky tenant:

      kubectl delete tenant bluesky\n

      This command removes the tenant resource from the cluster while leaving the specified namespaces untouched, adhering to the configured onDeletePurgeNamespaces policies. This approach provides flexibility in managing the lifecycle of tenant resources, catering to various operational strategies and compliance requirements.

      "},{"location":"tutorials/tenant/tenant-hibernation.html","title":"Hibernating a Tenant","text":"

      Implementing hibernation for tenants' namespaces efficiently manages cluster resources by temporarily reducing workload activities during off-peak hours. This guide demonstrates how to configure hibernation schedules for tenant namespaces, leveraging Tenant and ResourceSupervisor for precise control.

      "},{"location":"tutorials/tenant/tenant-hibernation.html#configuring-hibernation-for-tenant-namespaces","title":"Configuring Hibernation for Tenant Namespaces","text":"

      You can manage workloads in your cluster with MTO by implementing a hibernation schedule for your tenants. Hibernation downsizes the running Deployments and StatefulSets in a tenant\u2019s namespace according to a defined cron schedule. You can set a hibernation schedule for your tenants by adding the \u2018spec.hibernation\u2019 field to the tenant's respective Custom Resource.

      hibernation:\n  sleepSchedule: 23 * * * *\n  wakeSchedule: 26 * * * *\n

      spec.hibernation.sleepSchedule accepts a cron expression indicating the time to put the workloads in your tenant\u2019s namespaces to sleep.

      spec.hibernation.wakeSchedule accepts a cron expression indicating the time to wake the workloads in your tenant\u2019s namespaces up.

      Note

      Both sleep and wake schedules must be specified for your Hibernation schedule to be valid.

      Additionally, adding the hibernation.stakater.com/exclude: 'true' annotation to a namespace excludes it from hibernating.

      Note

      This is only true for hibernation applied via the Tenant Custom Resource, and does not apply for hibernation done by manually creating a ResourceSupervisor (details about that below).

      Note

      This will not wake up an already sleeping namespace before the wake schedule.

      "},{"location":"tutorials/tenant/tenant-hibernation.html#resource-supervisor","title":"Resource Supervisor","text":"

      Adding a Hibernation Schedule to a Tenant creates an accompanying ResourceSupervisor Custom Resource. The Resource Supervisor stores the Hibernation schedules and manages the current and previous states of all the applications, whether they are sleeping or awake.

      When the sleep timer is activated, the Resource Supervisor controller stores the details of your applications (including the number of replicas, configurations, etc.) in the applications' namespaces and then puts your applications to sleep. When the wake timer is activated, the controller wakes up the applications using their stored details.

      Enabling ArgoCD support for Tenants will also hibernate applications in the tenants' appProjects.

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: ResourceSupervisor\nmetadata:\n  name: sigma\nspec:\n  argocd:\n    appProjects:\n      - sigma\n    namespace: openshift-gitops\n  hibernation:\n    sleepSchedule: 42 * * * *\n    wakeSchedule: 45 * * * *\n  namespaces:\n    - tenant-ns1\n    - tenant-ns2\n

      Currently, Hibernation is available only for StatefulSets and Deployments.

      "},{"location":"tutorials/tenant/tenant-hibernation.html#manual-creation-of-resourcesupervisor","title":"Manual creation of ResourceSupervisor","text":"

      Hibernation can also be applied by creating a ResourceSupervisor resource manually. The ResourceSupervisor definition will contain the hibernation cron schedule, the names of the namespaces to be hibernated, and the names of the ArgoCD AppProjects whose ArgoCD Applications have to be hibernated (as per the given schedule).

      This method can be used to hibernate:

      • Some specific namespaces and AppProjects in a tenant
      • A set of namespaces and AppProjects belonging to different tenants
      • Namespaces and AppProjects belonging to a tenant that the cluster admin is not a member of
      • Non-tenant namespaces and ArgoCD AppProjects

      As an example, the following ResourceSupervisor could be created manually, to apply hibernation explicitly to the 'ns1' and 'ns2' namespaces, and to the 'sample-app-project' AppProject.

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: ResourceSupervisor\nmetadata:\n  name: hibernator\nspec:\n  argocd:\n    appProjects:\n      - sample-app-project\n    namespace: openshift-gitops\n  hibernation:\n    sleepSchedule: 42 * * * *\n    wakeSchedule: 45 * * * *\n  namespaces:\n    - ns1\n    - ns2\n
      "},{"location":"tutorials/tenant/tenant-hibernation.html#freeing-up-unused-resources-with-hibernation","title":"Freeing up unused resources with hibernation","text":""},{"location":"tutorials/tenant/tenant-hibernation.html#hibernating-a-tenant_1","title":"Hibernating a tenant","text":"

      Bill is a cluster administrator who wants to free up unused cluster resources at nighttime, in an effort to reduce costs (when the cluster isn't being used).

      First, Bill creates a tenant with the hibernation schedules mentioned in the spec, or adds the hibernation field to an existing tenant:

      apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: sigma\nspec:\n  hibernation:\n    sleepSchedule: \"0 20 * * 1-5\"  # Sleep at 8 PM on weekdays\n    wakeSchedule: \"0 8 * * 1-5\"    # Wake at 8 AM on weekdays\n  owners:\n    users:\n      - user@example.com\n  quota: medium\n  namespaces:\n    withoutTenantPrefix:\n      - dev\n      - stage\n      - build\n

      The schedules above will put all the Deployments and StatefulSets within the tenant's namespaces to sleep, by reducing their pod count to 0 at 8 PM every weekday. At 8 AM on weekdays, the namespaces will then wake up by restoring their applications' previous pod counts.

      Bill can verify this behaviour by checking the newly created ResourceSupervisor resource at run time:

      oc get ResourceSupervisor -A\nNAME           AGE\nsigma          5m\n

      The ResourceSupervisor will look like this at 'running' time (as per the schedule):

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: ResourceSupervisor\nmetadata:\n  name: example\nspec:\n  argocd:\n    appProjects: []\n    namespace: ''\n  hibernation:\n    sleepSchedule: 0 20 * * 1-5\n    wakeSchedule: 0 8 * * 1-5\n  namespaces:\n    - build\n    - stage\n    - dev\nstatus:\n  currentStatus: running\n  nextReconcileTime: '2022-10-12T20:00:00Z'\n

      The ResourceSupervisor will look like this at 'sleeping' time (as per the schedule):

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: ResourceSupervisor\nmetadata:\n  name: example\nspec:\n  argocd:\n    appProjects: []\n    namespace: ''\n  hibernation:\n    sleepSchedule: 0 20 * * 1-5\n    wakeSchedule: 0 8 * * 1-5\n  namespaces:\n    - build\n    - stage\n    - dev\nstatus:\n  currentStatus: sleeping\n  nextReconcileTime: '2022-10-13T08:00:00Z'\n  sleepingApplications:\n    - Namespace: build\n      kind: Deployment\n      name: example\n      replicas: 3\n    - Namespace: stage\n      kind: Deployment\n      name: example\n      replicas: 3\n

      Bill wants to prevent the build namespace from going to sleep, so he can add the hibernation.stakater.com/exclude: 'true' annotation to it. The ResourceSupervisor will now look like this after reconciling:

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: ResourceSupervisor\nmetadata:\n  name: example\nspec:\n  argocd:\n    appProjects: []\n    namespace: ''\n  hibernation:\n    sleepSchedule: 0 20 * * 1-5\n    wakeSchedule: 0 8 * * 1-5\n  namespaces:\n    - stage\n    - dev\nstatus:\n  currentStatus: sleeping\n  nextReconcileTime: '2022-10-13T08:00:00Z'\n  sleepingApplications:\n    - Namespace: stage\n      kind: Deployment\n      name: example\n      replicas: 3\n
      "},{"location":"tutorials/tenant/tenant-hibernation.html#hibernating-namespaces-andor-argocd-applications-with-resourcesupervisor","title":"Hibernating namespaces and/or ArgoCD Applications with ResourceSupervisor","text":"

      Bill, the cluster administrator, wants to hibernate a collection of namespaces and AppProjects belonging to multiple different tenants. He can do so by creating a ResourceSupervisor manually, specifying the hibernation schedule in its spec, the namespaces and ArgoCD Applications that need to be hibernated as per the mentioned schedule. Bill can also use the same method to hibernate some namespaces and ArgoCD Applications that do not belong to any tenant on his cluster.

      The example given below will hibernate the ArgoCD Applications in the 'test-app-project' AppProject; and it will also hibernate the 'ns2' and 'ns4' namespaces.

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: ResourceSupervisor\nmetadata:\n  name: test-resource-supervisor\nspec:\n  argocd:\n    appProjects:\n      - test-app-project\n    namespace: argocd-ns\n  hibernation:\n    sleepSchedule: 0 20 * * 1-5\n    wakeSchedule: 0 8 * * 1-5\n  namespaces:\n    - ns2\n    - ns4\nstatus:\n  currentStatus: sleeping\n  nextReconcileTime: '2022-10-13T08:00:00Z'\n  sleepingApplications:\n    - Namespace: ns2\n      kind: Deployment\n      name: test-deployment\n      replicas: 3\n
      "}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"index.html","title":"Introduction","text":"

      Kubernetes is designed to support a single tenant platform; OpenShift brings some improvements with its \"Secure by default\" concepts but it is still very complex to design and orchestrate all the moving parts involved in building a secure multi-tenant platform hence making it difficult for cluster admins to host multi-tenancy in a single OpenShift cluster. If multi-tenancy is achieved by sharing a cluster, it can have many advantages, e.g. efficient resource utilization, less configuration effort and easier sharing of cluster-internal resources among different tenants. OpenShift and all managed applications provide enough primitive resources to achieve multi-tenancy, but it requires professional skills and deep knowledge of OpenShift.

      This is where Multi Tenant Operator (MTO) comes in and provides easy to manage/configure multi-tenancy. MTO provides wrappers around OpenShift resources to provide a higher level of abstraction to users. With MTO admins can configure Network and Security Policies, Resource Quotas, Limit Ranges, RBAC for every tenant, which are automatically inherited by all the namespaces and users in the tenant. Depending on the user's role, they are free to operate within their tenants in complete autonomy. MTO supports initializing new tenants using GitOps management pattern. Changes can be managed via PRs just like a typical GitOps workflow, so tenants can request changes, add new users, or remove users.

      The idea of MTO is to use namespaces as independent sandboxes, where tenant applications can run independently of each other. Cluster admins shall configure MTO's custom resources, which then become a self-service system for tenants. This minimizes the efforts of the cluster admins.

      MTO enables cluster admins to host multiple tenants in a single OpenShift Cluster, i.e.:

      • Share an OpenShift cluster with multiple tenants
      • Share managed applications with multiple tenants
      • Configure and manage tenants and their sandboxes

      MTO is also OpenShift certified

      "},{"location":"index.html#features","title":"Features","text":"

      The major features of Multi Tenant Operator (MTO) are described below.

      "},{"location":"index.html#kubernetes-multitenancy","title":"Kubernetes Multitenancy","text":"

      RBAC is one of the most complicated and error-prone parts of Kubernetes. With Multi Tenant Operator, you can rest assured that RBAC is configured with the \"least privilege\" mindset and all rules are kept up-to-date with zero manual effort.

      Multi Tenant Operator binds existing ClusterRoles to the Tenant's Namespaces used for managing access to the Namespaces and the resources they contain. You can also modify the default roles or create new roles to have full control and customize access control for your users and teams.

      Multi Tenant Operator is also able to leverage existing OpenShift groups or external groups synced from 3rd party identity management systems, for maintaining Tenant membership in your organization's current user management system.

      "},{"location":"index.html#hashicorp-vault-multitenancy","title":"HashiCorp Vault Multitenancy","text":"

      Multi Tenant Operator extends the tenants permission model to HashiCorp Vault where it can create Vault paths and greatly ease the overhead of managing RBAC in Vault. Tenant users can manage their own secrets without the concern of someone else having access to their Vault paths.

      More details on Vault Multitenancy

      "},{"location":"index.html#argocd-multitenancy","title":"ArgoCD Multitenancy","text":"

      Multi Tenant Operator is not only providing strong Multi Tenancy for the OpenShift internals but also extends the tenants permission model to ArgoCD were it can provision AppProjects and Allowed Repositories for your tenants greatly ease the overhead of managing RBAC in ArgoCD.

      More details on ArgoCD Multitenancy

      "},{"location":"index.html#resource-management","title":"Resource Management","text":"

      Multi Tenant Operator provides a mechanism for defining Resource Quotas at the tenant scope, meaning all namespaces belonging to a particular tenant share the defined quota, which is why you are able to safely enable dev teams to self serve their namespaces whilst being confident that they can only use the resources allocated based on budget and business needs.

      More details on Quota

      "},{"location":"index.html#templates-and-template-distribution","title":"Templates and Template distribution","text":"

      Multi Tenant Operator allows admins/users to define templates for namespaces, so that others can instantiate these templates to provision namespaces with batteries loaded. A template could pre-populate a namespace for certain use cases or with basic tooling required. Templates allow you to define Kubernetes manifests, Helm chart and more to be applied when the template is used to create a namespace.

      It also allows the parameterizing of these templates for flexibility and ease of use. It also provides the option to enforce the presence of templates in one tenant's or all the tenants' namespaces for configuring secure defaults.

      Common use cases for namespace templates may be:

      • Adding networking policies for multitenancy
      • Adding development tooling to a namespace
      • Deploying pre-populated databases with test data
      • Injecting new namespaces with optional credentials such as image pull secrets

      More details on Distributing Template Resources

      "},{"location":"index.html#mto-console","title":"MTO Console","text":"

      Multi Tenant Operator Console is a comprehensive user interface designed for both administrators and tenant users to manage multi-tenant environments. The MTO Console simplifies the complexity involved in handling various aspects of tenants and their related resources. It serves as a centralized monitoring hub, offering insights into the current state of tenants, namespaces, templates and quotas. It is designed to provide a quick summary/snapshot of MTO's status and facilitates easier interaction with various resources such as tenants, namespaces, templates, and quotas.

      More details on Console

      "},{"location":"index.html#showback","title":"Showback","text":"

      The showback functionality in Multi Tenant Operator (MTO) Console is a significant feature designed to enhance the management of resources and costs in multi-tenant Kubernetes environments. This feature focuses on accurately tracking the usage of resources by each tenant, and/or namespace, enabling organizations to monitor and optimize their expenditures. Furthermore, this functionality supports financial planning and budgeting by offering a clear view of operational costs associated with each tenant. This can be particularly beneficial for organizations that chargeback internal departments or external clients based on resource usage, ensuring that billing is fair and reflective of actual consumption.

      More details on Showback

      "},{"location":"index.html#hibernation","title":"Hibernation","text":"

      Multi Tenant Operator can downscale Deployments and StatefulSets in a tenant's Namespace according to a defined sleep schedule. The Deployments and StatefulSets are brought back to their required replicas according to the provided wake schedule.

      More details on Hibernation

      "},{"location":"index.html#mattermost-multitenancy","title":"Mattermost Multitenancy","text":"

      Multi Tenant Operator can manage Mattermost to create Teams for tenant users. All tenant users get a unique team and a list of predefined channels gets created. When a user is removed from the tenant, the user is also removed from the Mattermost team corresponding to tenant.

      More details on Mattermost

      "},{"location":"index.html#remote-development-namespaces","title":"Remote Development Namespaces","text":"

      Multi Tenant Operator can be configured to automatically provision a namespace in the cluster for every member of the specific tenant, that will also be preloaded with any selected templates and consume the same pool of resources from the tenants quota creating safe remote dev namespaces that teams can use as scratch namespace for rapid prototyping and development. So, every developer gets a Kubernetes-based cloud development environment that feel like working on localhost.

      More details on Sandboxes

      "},{"location":"index.html#cross-namespace-resource-distribution","title":"Cross Namespace Resource Distribution","text":"

      Multi Tenant Operator supports cloning of secrets and configmaps from one namespace to another namespace based on label selectors. It uses templates to enable users to provide reference to secrets and configmaps. It uses a template group instance to distribute those secrets and namespaces in matching namespaces, even if namespaces belong to different tenants. If template instance is used then the resources will only be mapped if namespaces belong to same tenant.

      More details on Copying Secrets and ConfigMaps

      "},{"location":"index.html#self-service","title":"Self-Service","text":"

      With Multi Tenant Operator, you can empower your users to safely provision namespaces for themselves and their teams (typically mapped to SSO groups). Team-owned namespaces and the resources inside them count towards the team's quotas rather than the user's individual limits and are automatically shared with all team members according to the access rules you configure in Multi Tenant Operator.

      Also, by leveraging Multi Tenant Operator's templating mechanism, namespaces can be provisioned and automatically pre-populated with any kind of resource or multiple resources such as network policies, docker pull secrets or even Helm charts etc

      "},{"location":"index.html#everything-as-codegitops-ready","title":"Everything as Code/GitOps Ready","text":"

      Multi Tenant Operator is designed and built to be 100% OpenShift-native and to be configured and managed the same familiar way as native OpenShift resources so is perfect for modern shops that are dedicated to GitOps as it is fully configurable using Custom Resources.

      "},{"location":"index.html#preventing-clusters-sprawl","title":"Preventing Clusters Sprawl","text":"

      As companies look to further harness the power of cloud-native, they are adopting container technologies at rapid speed, increasing the number of clusters and workloads. As the number of Kubernetes clusters grows, this is an increasing work for the Ops team. When it comes to patching security issues or upgrading clusters, teams are doing five times the amount of work.

      With Multi Tenant Operator teams can share a single cluster with multiple teams, groups of users, or departments by saving operational and management efforts. This prevents you from Kubernetes cluster sprawl.

      "},{"location":"index.html#native-experience","title":"Native Experience","text":"

      Multi Tenant Operator provides multi-tenancy with a native Kubernetes experience without introducing additional management layers, plugins, or customized binaries.

      "},{"location":"changelog.html","title":"Changelog","text":""},{"location":"changelog.html#v012x","title":"v0.12.x","text":""},{"location":"changelog.html#v01219","title":"v0.12.19","text":""},{"location":"changelog.html#fix","title":"Fix","text":"
      • Fixed a recurring issue in the Extensions controller where status changes were triggering unnecessary reconciliation loops.
      • Resolved a visibility issue where labels and annotations for sandbox namespaces were not appearing in the extension's status.
      • Addressed an issue where AppProject was being deleted upon extension CR deletion, regardless of the onDeletePurgeAppProject field value.
      • Optimized memory usage for Keycloak to address high consumption issues.
      • Resolved an issue that was causing a panic in the Extension Controller when the IntegrationConfig (IC) was not present.
      • Fixed an issue where the status was not being correctly updated when the entire Metadata block was removed from the Tenant specification.
      "},{"location":"changelog.html#v01213","title":"v0.12.13","text":""},{"location":"changelog.html#fix_1","title":"Fix","text":"
      • Resolved an issue that was preventing Vault from authenticating using the kubernetes authentication method.
      • Addressed an issue where changes to the IntegrationConfig were not updating the destination namespaces in ArgoCD's AppProject.
      • Fixed a problem where the tenant controller was preventing namespace deletion if it failed to delete external dependencies, such as Vault.
      "},{"location":"changelog.html#v0121","title":"v0.12.1","text":""},{"location":"changelog.html#fix_2","title":"Fix","text":"
      • Resolved memory consumption problems in multiple controllers by reducing the number of reconciliations.
      "},{"location":"changelog.html#v0120","title":"v0.12.0","text":""},{"location":"changelog.html#feature","title":"Feature","text":""},{"location":"changelog.html#enhanced","title":"Enhanced","text":"
      • Updated Tenant CR to v1beta3, more details in Tenant CRD
      • Added custom pricing support for Opencost, more details in Opencost
      "},{"location":"changelog.html#fix_3","title":"Fix","text":"
      • Resolved an issue in Templates that prevented the deployment of public helm charts.
      "},{"location":"changelog.html#v011x","title":"v0.11.x","text":""},{"location":"changelog.html#v0110","title":"v0.11.0","text":""},{"location":"changelog.html#feature_1","title":"Feature","text":"
      • Added support for configuring external keycloak in integrationconfig.
      • Added free tier support that allows creation of 2 tenants without license.
      "},{"location":"changelog.html#v010x","title":"v0.10.x","text":""},{"location":"changelog.html#v0106","title":"v0.10.6","text":""},{"location":"changelog.html#fix_4","title":"Fix","text":"
      • Fixed broken logs for namespace webhook where username and namespace were interchangeably used after a recent update
      "},{"location":"changelog.html#enhanced_1","title":"Enhanced","text":"
      • Made log messages more elaborative and consistent on one format for namespace webhook
      "},{"location":"changelog.html#v0105","title":"v0.10.5","text":""},{"location":"changelog.html#fix_5","title":"Fix","text":"
      • TemplateGroupInstance controller now correctly updates the TemplateGroupInstance custom resource status and the namespace count upon the deletion of a namespace.
      • Conflict between TemplateGroupInstance controller and kube-contoller-manager over mentioning of secret names in secrets or imagePullSecrets field in ServiceAccounts has been fixed by temporarily ignoring updates to or from ServiceAccounts.
      "},{"location":"changelog.html#enhanced_2","title":"Enhanced","text":"
      • Privileged service accounts mentioned in the IntegrationConfig have now access over all types of namespaces. Previously operations were denied on orphaned namespaces (the namespaces which are not part of both privileged and tenant scope). More info in Troubleshooting Guide
      • TemplateGroupInstance controller now ensures that its underlying resources are force-synced when a namespace is created or deleted.
      • Optimizations were made to ensure the reconciler in the TGI controller runs only once per watch event, reducing reconcile times.
      • The TemplateGroupInstance reconcile flow has been refined to process only the namespace for which the event was received, streamlining resource creation/deletion and improving overall efficiency.
      • Introduced new metrics to enhance the monitoring capabilities of the operator. Details at TGI Metrics Explanation
      "},{"location":"changelog.html#v0100","title":"v0.10.0","text":""},{"location":"changelog.html#feature_2","title":"Feature","text":"
      • Added support for caching for MTO Console using PostgreSQL as caching layer.
      • Added support for custom metrics with Template, Template Instance and Template Group Instance.
      • Graph visualization of Tenant and its associated resources on MTO Console.
      • Tenant and Admin level authz/authn support within MTO Console and Gateway.
      • Now in MTO console you can view cost of different Tenant resources with different date, resource type and additional filters.
      • MTO can now create a default keycloak realm, client and mto-admin user for Console.
      • Implemented Cluster Resource Quota for vanilla Kubernetes platform type.
      • Dependency of TLS secrets for MTO Webhook.
      • Added Helm Chart that would be used for installing MTO over Kubernetes.
        • And it comes with default Cert Manager manifests for certificates.
      • Support for MTO e2e.
      "},{"location":"changelog.html#fix_6","title":"Fix","text":"
      • Updated CreateMergePatch to MergeMergePatches to address issues caused by losing resourceVersion and UID when converting oldObject to newObject. This prevents problems when the object is edited by another controller.
      • In Template Resource distribution for Secret type, we now consider the source's Secret field type, preventing default creation as Opaque regardless of the source's actual type.
      • Enhanced admin permissions for tenant role in Vault to include Create, Update, Delete alongside existing Read and List privileges for the common-shared-secrets path. Viewers now have Read permission.
      "},{"location":"changelog.html#enhanced_3","title":"Enhanced","text":"
      • Started to support Kubernetes along with OpenShift as platform type.
      • Support of MTO's PostgreSQL instance as persistent storage for keycloak.
      • kube:admin is now bypassed by default to perform operations, earlier kube:admin needed to be mentioned in respective tenants to give it access over namespaces.
      "},{"location":"changelog.html#v09x","title":"v0.9.x","text":""},{"location":"changelog.html#v094","title":"v0.9.4","text":"
      • enhance: Removed Quota's default support of adding it to Tenant CR in spec.quota, if quota.tenantoperator.stakater.com/is-default: \"true\" annotation is present
      • fix: ValidatingWebhookConfiguration CRs are now owned by OLM, to handle cleanup upon operator uninstall
      • enhance: TemplateGroupInstance CRs now actively watch the resources they apply, and perform functions to make sure they are in sync with the state mentioned in their respective Templates

      More information about TemplateGroupInstance's sync at Sync Resources Deployed by TemplateGroupInstance

      "},{"location":"changelog.html#v092","title":"v0.9.2","text":"
      • fix: Values within TemplateInstances created via Tenants will no longer be duplicated on Tenant CR update
      • fix: Fixed a bug that made private namespaces become public
      "},{"location":"changelog.html#v091","title":"v0.9.1","text":"
      • fix: Allow namespace controller to reconcile without crashing, if no IC exists
      • fix: In case a group mentioned in IC doesn't exist, it won't block reconciliation or editing of MTO's manifests
      "},{"location":"changelog.html#v090","title":"v0.9.0","text":"
      • feat: Added console for tenants, templates and integration config
      • feat: Added support for custom realm name for RHSSO integration in Integration Config
      • feat: Add multiple status conditions to tenant and TGI for success and failure cases
      • feat: Show error messages with tenant and TGI status
      • fix: Stop reconciliation breaking for tenant and TGI, instead continue and show warnings
      • fix: Disable TGI/TI reconcile if mentioned template is not found.
      • fix: Disable repeated users webhook in tenant
      • enhance: Reduced API calls
      • enhance: General enhancements and improvements
      • chore: Update dependencies
      "},{"location":"changelog.html#enabling-console","title":"Enabling console","text":"
      • To enable console visit Installation, and add config to subscription for OperatorHub based installation.
      "},{"location":"changelog.html#v08x","title":"v0.8.x","text":""},{"location":"changelog.html#v083","title":"v0.8.3","text":"
      • fix: Reconcile namespaces when the group spec for tenants is changed, so new rolebindings can be created for them
      "},{"location":"changelog.html#v081","title":"v0.8.1","text":"
      • fix: Updated release pipelines
      "},{"location":"changelog.html#v080","title":"v0.8.0","text":"
      • feat: Allow custom roles for each tenant via label selector, more details in custom roles document
        • Roles mapping is a required field in MTO's IntegrationConfig. By default, it will always be filled with OpenShift's admin/edit/view roles
        • Ensure that mentioned roles exist within the cluster
        • Remove coupling with OpenShift's built-in admin/edit/view roles
      • feat: Removed coupling of ResourceSupervisor and Tenant resources
        • Added list of namespaces to hibernate within the ResourceSupervisor resource
        • Ensured that the same namespace cannot be added to two different Resource Supervisors
        • Moved ResourceSupervisor into a separate pod
        • Improved logs
      • fix: Remove bug from tenant's common and specific metadata
      • fix: Add missing field to Tenant's conversion webhook
      • fix: Fix panic in ResourceSupervisor sleep functionality due to sending on closed channel
      • chore: Update dependencies
      "},{"location":"changelog.html#v07x","title":"v0.7.x","text":""},{"location":"changelog.html#v074","title":"v0.7.4","text":"
      • maintain: Automate certification of new MTO releases on RedHat's Operator Hub
      "},{"location":"changelog.html#v073","title":"v0.7.3","text":"
      • feat: Updated Tenant CR to provide Tenant level AppProject permissions
      "},{"location":"changelog.html#v072","title":"v0.7.2","text":"
      • feat: Add support to map secrets/configmaps from one namespace to other namespaces using TI. Secrets/configmaps will only be mapped if their namespaces belong to same Tenant
      "},{"location":"changelog.html#v071","title":"v0.7.1","text":"
      • feat: Add option to keep AppProjects created by Multi Tenant Operator in case Tenant is deleted. By default, AppProjects get deleted
      • fix: Status now updates after namespaces are created
      • maintain: Changes to Helm chart's default behaviour
      "},{"location":"changelog.html#v070","title":"v0.7.0","text":"
      • feat: Add support to map secrets/configmaps from one namespace to other namespaces using TGI. Resources can be mapped from one Tenant's namespaces to some other Tenant's namespaces
      • feat: Allow creation of sandboxes that are private to the user
      • feat: Allow creation of namespaces without tenant prefix from within tenant spec
      • fix: Webhook changes will now be updated without manual intervention
      • maintain: Updated Tenant CR version from v1beta1 to v1beta2. Conversion webhook is added to facilitate transition to new version
        • see Tenant spec for updated spec
      • enhance: Better automated testing
      "},{"location":"changelog.html#v06x","title":"v0.6.x","text":""},{"location":"changelog.html#v061","title":"v0.6.1","text":"
      • fix: Update MTO service-account name in environment variable
      "},{"location":"changelog.html#v060","title":"v0.6.0","text":"
      • feat: Add support to ArgoCD AppProjects created by Tenant Controller to have their sync disabled when relevant namespaces are hibernating
      • feat: Add validation webhook for ResourceSupervisor
      • fix: Delete ResourceSupervisor when hibernation is removed from tenant CR
      • fix: CRQ and limit range not updating when quota changes
      • fix: ArgoCD AppProjects created by Tenant Controller not updating when Tenant label is added to an existing namespace
      • fix: Namespace workflow for TGI
      • fix: ResourceSupervisor deletion workflow
      • fix: Update RHSSO user filter for Vault integration
      • fix: Update regex of namespace names in tenant CRD
      • enhance: Optimize TGI and TI performance under load
      • maintain: Bump Operator-SDK and Dependencies version
      "},{"location":"changelog.html#v05x","title":"v0.5.x","text":""},{"location":"changelog.html#v054","title":"v0.5.4","text":"
      • fix: Update Helm dependency to v3.8.2
      "},{"location":"changelog.html#v053","title":"v0.5.3","text":"
      • fix: Add support for parameters in Helm chartRepository in templates
      "},{"location":"changelog.html#v052","title":"v0.5.2","text":"
      • fix: Add service name prefix for webhooks
      "},{"location":"changelog.html#v051","title":"v0.5.1","text":"
      • fix: ResourceSupervisor CR no longer requires a field for the Tenant name
      "},{"location":"changelog.html#v050","title":"v0.5.0","text":"
      • feat: Add support for tenant namespaces off-boarding. For more details check out onDelete
      • feat: Add tenant webhook for spec validation

      • fix: TemplateGroupInstance now cleans up leftover Template resources from namespaces that are no longer part of TGI namespace selector

      • fix: Fixed hibernation sync issue

      • enhance: Update tenant spec for applying common/specific namespace labels/annotations. For more details check out commonMetadata & SpecificMetadata

      • enhance: Add support for multi-pod architecture for Operator-Hub

      • chore: Remove conversion webhook for Quota and Tenant

      "},{"location":"changelog.html#v04x","title":"v0.4.x","text":""},{"location":"changelog.html#v047","title":"v0.4.7","text":"
      • feat: Add hibernation of StatefulSets and Deployments based on a timer
      • feat: New custom resource that handles hibernation
      "},{"location":"changelog.html#v046","title":"v0.4.6","text":"
      • fix: Revert v0.4.4
      "},{"location":"changelog.html#v045","title":"v0.4.5","text":"
      • feat: Add support for applying labels/annotation on specific namespaces
      "},{"location":"changelog.html#v044","title":"v0.4.4","text":"
      • fix: Update privilegedNamespaces regex
      "},{"location":"changelog.html#v043","title":"v0.4.3","text":"
      • fix: IntegrationConfig will now be synced in all pods
      "},{"location":"changelog.html#v042","title":"v0.4.2","text":"
      • feat: Added support to distribute common labels and annotations to tenant namespaces
      "},{"location":"changelog.html#v041","title":"v0.4.1","text":"
      • fix: Update dependencies to latest version
      "},{"location":"changelog.html#v040","title":"v0.4.0","text":"
      • feat: Controllers are now separated into individual pods
      "},{"location":"changelog.html#v03x","title":"v0.3.x","text":""},{"location":"changelog.html#v0333","title":"v0.3.33","text":"
      • fix: Optimize namespace reconciliation
      "},{"location":"changelog.html#v0333_1","title":"v0.3.33","text":"
      • fix: Revert v0.3.29 change till webhook network issue isn't resolved
      "},{"location":"changelog.html#v0333_2","title":"v0.3.33","text":"
      • fix: Execute webhook and controller of matching custom resource in same pod
      "},{"location":"changelog.html#v0330","title":"v0.3.30","text":"
      • feat: Namespace controller will now trigger TemplateGroupInstance when a new matching namespace is created
      "},{"location":"changelog.html#v0329","title":"v0.3.29","text":"
      • feat: Controllers are now separated into individual pods
      "},{"location":"changelog.html#v0328","title":"v0.3.28","text":"
      • fix: Enhancement of TemplateGroupInstance Namespace event listener
      "},{"location":"changelog.html#v0327","title":"v0.3.27","text":"
      • feat: TemplateGroupInstance will create resources instantly whenever a Namespace with matching labels is created
      "},{"location":"changelog.html#v0326","title":"v0.3.26","text":"
      • fix: Update reconciliation frequency of TemplateGroupInstance
      "},{"location":"changelog.html#v0325","title":"v0.3.25","text":"
      • feat: TemplateGroupInstance will now directly create template resources instead of creating TemplateInstances
      "},{"location":"changelog.html#migrating-from-pervious-version","title":"Migrating from pervious version","text":"
      • To migrate to Tenant-Operator:v0.3.25 perform the following steps
        • Downscale Tenant-Operator deployment by setting the replicas count to 0
        • Delete TemplateInstances created by TemplateGroupInstance (Naming convention of TemplateInstance created by TemplateGroupInstance is group-{Template.Name})
        • Update version of Tenant-Operator to v0.3.25 and set the replicas count to 2. After Tenant-Operator pods are up TemplateGroupInstance will create the missing resources
      "},{"location":"changelog.html#v0324","title":"v0.3.24","text":"
      • feat: Add feature to allow ArgoCD to sync specific cluster scoped custom resources, configurable via Integration Config. More details in relevant docs
      "},{"location":"changelog.html#v0323","title":"v0.3.23","text":"
      • feat: Added concurrent reconcilers for template instance controller
      "},{"location":"changelog.html#v0322","title":"v0.3.22","text":"
      • feat: Added validation webhook to prevent Tenant owners from creating RoleBindings with kind 'Group' or 'User'
      • fix: Removed redundant logs for namespace webhook
      • fix: Added missing check for users in a tenant owner's groups in namespace validation webhook
      • fix: General enhancements and improvements

      \u26a0\ufe0f Known Issues

      • caBundle field in validation webhooks is not being populated for newly added webhooks. A temporary fix is to edit the validation webhook configuration manifest without the caBundle field added in any webhook, so OpenShift can add it to all fields simultaneously
        • Edit the ValidatingWebhookConfiguration multi-tenant-operator-validating-webhook-configuration by removing all the caBundle fields of all webhooks
        • Save the manifest
        • Verify that all caBundle fields have been populated
        • Restart Tenant-Operator pods
      "},{"location":"changelog.html#v0321","title":"v0.3.21","text":"
      • feat: Added ClusterRole manager rules extension
      "},{"location":"changelog.html#v0320","title":"v0.3.20","text":"
      • fix: Fixed the recreation of underlying template resources, if resources were deleted
      "},{"location":"changelog.html#v0319","title":"v0.3.19","text":"
      • feat: Namespace webhook FailurePolicy is now set to Ignore instead of Fail
      • fix: Fixed config not being updated in namespace webhook when Integration Config is updated
      • fix: Fixed a crash that occurred in case of ArgoCD in Integration Config was not set during deletion of Tenant resource

      \u26a0\ufe0f ApiVersion v1alpha1 of Tenant and Quota custom resources has been deprecated and is scheduled to be removed in the future. The following links contain the updated structure of both resources

      • Quota v1beta1
      • Tenant v1beta1
      "},{"location":"changelog.html#v0318","title":"v0.3.18","text":"
      • fix: Add ArgoCD namespace to destination namespaces for App Projects
      "},{"location":"changelog.html#v0317","title":"v0.3.17","text":"
      • fix: Cluster administrator's permission will now have higher precedence on privileged namespaces
      "},{"location":"changelog.html#v0316","title":"v0.3.16","text":"
      • fix: Add groups mentioned in Tenant CR to ArgoCD App Project manifests' RBAC
      "},{"location":"changelog.html#v0315","title":"v0.3.15","text":"
      • feat: Add validation webhook for TemplateInstance & TemplateGroupInstance to prevent their creation in case the Template they reference does not exist
      "},{"location":"changelog.html#v0314","title":"v0.3.14","text":"
      • feat: Added Validation Webhook for Quota to prevent its deletion when a reference to it exists in any Tenant
      • feat: Added Validation Webhook for Template to prevent its deletion when a reference to it exists in any Tenant, TemplateGroupInstance or TemplateInstance
      • fix: Fixed a crash that occurred in case Integration Config was not found
      "},{"location":"changelog.html#v0313","title":"v0.3.13","text":"
      • feat: Multi Tenant Operator will now consider all namespaces to be managed if any default Integration Config is not found
      "},{"location":"changelog.html#v0312","title":"v0.3.12","text":"
      • fix: General enhancements and improvements
      "},{"location":"changelog.html#v0311","title":"v0.3.11","text":"
      • fix: Fix Quota's conversion webhook converting the wrong LimitRange field
      "},{"location":"changelog.html#v0310","title":"v0.3.10","text":"
      • fix: Fix Quota's LimitRange to its intended design by being an optional field
      "},{"location":"changelog.html#v039","title":"v0.3.9","text":"
      • feat: Add ability to prevent certain resources from syncing via ArgoCD
      "},{"location":"changelog.html#v038","title":"v0.3.8","text":"
      • feat: Add default annotation to OpenShift Projects that show description about the Project
      "},{"location":"changelog.html#v037","title":"v0.3.7","text":"
      • fix: Fix a typo in Multi Tenant Operator's Helm release
      "},{"location":"changelog.html#v036","title":"v0.3.6","text":"
      • fix: Fix ArgoCD's destinationNamespaces created by Multi Tenant Operator
      "},{"location":"changelog.html#v035","title":"v0.3.5","text":"
      • fix: Change sandbox creation from 1 for each group to 1 for each user in a group
      "},{"location":"changelog.html#v034","title":"v0.3.4","text":"
      • feat: Support creation of sandboxes for each group
      "},{"location":"changelog.html#v033","title":"v0.3.3","text":"
      • feat: Add ability to create namespaces from a list of namespace prefixes listed in the Tenant CR
      "},{"location":"changelog.html#v032","title":"v0.3.2","text":"
      • refactor: Restructure Quota CR, more details in relevant docs
      • feat: Add support for adding LimitRanges in Quota
      • feat: Add conversion webhook to convert existing v1alpha1 versions of quota to v1beta1
      "},{"location":"changelog.html#v031","title":"v0.3.1","text":"
      • feat: Add ability to create ArgoCD AppProjects per tenant, more details in relevant docs
      "},{"location":"changelog.html#v030","title":"v0.3.0","text":"
      • feat: Add support to add groups in addition to users as tenant members
      "},{"location":"changelog.html#v02x","title":"v0.2.x","text":""},{"location":"changelog.html#v0233","title":"v0.2.33","text":"
      • refactor: Restructure Tenant spec, more details in relevant docs
      • feat: Add conversion webhook to convert existing v1alpha1 versions of tenant to v1beta1
      "},{"location":"changelog.html#v0232","title":"v0.2.32","text":"
      • refactor: Restructure integration config spec, more details in relevant docs
      • feat: Allow users to input custom regex in certain fields inside of integration config, more details in relevant docs
      "},{"location":"changelog.html#v0231","title":"v0.2.31","text":"
      • feat: Add limit range for kube-RBAC-proxy
      "},{"location":"eula.html","title":"Multi Tenant Operator End User License Agreement","text":"

      Last revision date: 12 December 2022

      IMPORTANT: THIS SOFTWARE END-USER LICENSE AGREEMENT (\"EULA\") IS A LEGAL AGREEMENT (\"Agreement\") BETWEEN YOU (THE CUSTOMER, EITHER AS AN INDIVIDUAL OR, IF PURCHASED OR OTHERWISE ACQUIRED BY OR FOR AN ENTITY, AS AN ENTITY) AND Stakater AB OR ITS SUBSIDUARY (\"COMPANY\"). READ IT CAREFULLY BEFORE COMPLETING THE INSTALLATION PROCESS AND USING MULTI TENANT OPERATOR (\"SOFTWARE\"). IT PROVIDES A LICENSE TO USE THE SOFTWARE AND CONTAINS WARRANTY INFORMATION AND LIABILITY DISCLAIMERS. BY INSTALLING AND USING THE SOFTWARE, YOU ARE CONFIRMING YOUR ACCEPTANCE OF THE SOFTWARE AND AGREEING TO BECOME BOUND BY THE TERMS OF THIS AGREEMENT.

      In order to use the Software under this Agreement, you must receive a license key at the time of purchase, in accordance with the scope of use and other terms specified and as set forth in Section 1 of this Agreement.

      "},{"location":"eula.html#1-license-grant","title":"1. License Grant","text":"
      • 1.1 General Use. This Agreement grants you a non-exclusive, non-transferable, limited license to the use rights for the Software, subject to the terms and conditions in this Agreement. The Software is licensed, not sold.

      • 1.2 Electronic Delivery. All Software and license documentation shall be delivered by electronic means unless otherwise specified on the applicable invoice or at the time of purchase. Software shall be deemed delivered when it is made available for download for you by the Company (\"Delivery\").

      "},{"location":"eula.html#2-modifications","title":"2. Modifications","text":"
      • 2.1 No Modifications may be created of the original Software. \"Modification\" means:

        • (a) Any addition to or deletion from the contents of a file included in the original Software

        • (b) Any new file that contains any part of the original Software

      "},{"location":"eula.html#3-restricted-uses","title":"3. Restricted Uses","text":"
      • 3.1 You shall not (and shall not allow any third party to):

        • (a) reverse engineer the Software or attempt to reconstruct or discover any source code, underlying ideas, algorithms, file formats or programming interfaces of the Software by any means whatsoever (except and only to the extent that applicable law prohibits or restricts reverse engineering restrictions);

        • (b) distribute, sell, sub-license, rent, lease or use the Software for time sharing, hosting, service provider or like purposes, except as expressly permitted under this Agreement;

        • (c) redistribute the Software;

        • (d) remove any product identification, proprietary, copyright or other notices contained in the Software;

        • (e) modify any part of the Software, create a derivative work of any part of the Software (except as permitted in Section 4), or incorporate the Software, except to the extent expressly authorized in writing by the Company;

        • (f) publicly disseminate performance information or analysis (including, without limitation, benchmarks) from any source relating to the Software;

        • (g) utilize any equipment, device, software, or other means designed to circumvent or remove any form of Source URL or copy protection used by the Company in connection with the Software, or use the Software together with any authorization code, Source URL, serial number, or other copy protection device not supplied by the Company;

        • (h) use the Software to develop a product which is competitive with any of the Company's product offerings;

        • (i) use unauthorized Source URLs or license key(s) or distribute or publish Source URLs or license key(s), except as may be expressly permitted by the Company in writing. If your unique license is ever published, the Company reserves the right to terminate your access without notice.

      • 3.2 Under no circumstances may you use the Software as part of a product or service that provides similar functionality to the Software itself.

      "},{"location":"eula.html#4-ownership","title":"4. Ownership","text":"
      • 4.1 Notwithstanding anything to the contrary contained herein, except for the limited license rights expressly provided herein, the Company and its suppliers have and will retain all rights, title and interest (including, without limitation, all patent, copyright, trademark, trade secret and other intellectual property rights) in and to the Software and all copies, modifications and derivative works thereof (including any changes which incorporate any of your ideas, feedback or suggestions). You acknowledge that you are obtaining only a limited license right to the Software, and that irrespective of any use of the words \"purchase\", \"sale\" or like terms hereunder no ownership rights are being conveyed to you under this Agreement or otherwise.
      "},{"location":"eula.html#5-fees-and-payment","title":"5. Fees and Payment","text":"
      • 5.1 The Software license fees will be due and payable in full as set forth in the applicable invoice or at the time of purchase. You shall be responsible for all taxes, with-holdings, duties and levies arising from the order (excluding taxes based on the net income of the Company).
      "},{"location":"eula.html#6-support-maintenance-and-services","title":"6. Support, Maintenance and Services","text":"
      • 6.1 Subject to the terms and conditions of this Agreement, as set forth in your invoice, and as set forth on the Stakater support page, support and maintenance services may be included with the purchase of your license subscription.
      "},{"location":"eula.html#7-disclaimer-of-warranties","title":"7. Disclaimer of Warranties","text":"
      • 7.1 The Software is provided \"as is\", with all faults, defects and errors, and without warranty of any kind. The Company does not warrant that the Software will be free of bugs, errors, or other defects, and the Company shall have no liability of any kind for the use of or inability to use the Software, the Software content or any associated service, and you acknowledge that it is not technically practicable for the Company to do so.

      • 7.2 To the maximum extent permitted by applicable law, the Company disclaims all warranties, express, implied, arising by law or otherwise, regarding the Software, the Software content and their respective performance or suitability for your intended use, including without limitation any implied warranty of merchantability, fitness for a particular purpose.

      "},{"location":"eula.html#8-limitation-of-liability","title":"8. Limitation of Liability","text":"
      • 8.1 In no event will the Company be liable for any direct, indirect, consequential, incidental, special, exemplary, or punitive damages or liabilities whatsoever arising from or relating to the Software, the Software content or this Agreement, whether based on contract, tort (including negligence), strict liability or other theory, even if the Company has been advised of the possibility of such damages.

      • 8.2 In no event will the Company's liability exceed the Software license price as indicated in the invoice. The existence of more than one claim will not enlarge or extend this limit.

      "},{"location":"eula.html#9-remedies","title":"9. Remedies","text":"
      • 9.1 Your exclusive remedy and the Company's entire liability for breach of this Agreement shall be limited, at the Company's sole and exclusive discretion, to:

        • (a) replacement of any defective software or documentation; or

        • (b) refund of the license fee paid to the Company

      "},{"location":"eula.html#10-acknowledgements","title":"10. Acknowledgements","text":"
      • 10.1 Consent to the Use of Data. You agree that the Company and its affiliates may collect and use technical information gathered as part of the product support services. The Company may use this information solely to improve products and services and will not disclose this information in a form that personally identifies individuals or organizations.

      • 10.2 Government End Users. If the Software and related documentation are supplied to or purchased by or on behalf of a Government, then the Software is deemed to be \"commercial software\" as that term is used in the acquisition regulation system.

      "},{"location":"eula.html#11-third-party-software","title":"11. Third Party Software","text":"
      • 11.1 Examples included in Software may provide links to third party libraries or code (collectively \"Third Party Software\") to implement various functions. Third Party Software does not comprise part of the Software. In some cases, access to Third Party Software may be included along with the Software delivery as a convenience for demonstration purposes. Licensee acknowledges:

        • (1) That some part of Third Party Software may require additional licensing of copyright and patents from the owners of such, and

        • (2) That distribution of any of the Software referencing or including any portion of a Third Party Software may require appropriate licensing from such third parties

      "},{"location":"eula.html#12-miscellaneous","title":"12. Miscellaneous","text":"
      • 12.1 Entire Agreement. This Agreement sets forth our entire agreement with respect to the Software and the subject matter hereof and supersedes all prior and contemporaneous understandings and agreements whether written or oral.

      • 12.2 Amendment. The Company reserves the right, in its sole discretion, to amend this Agreement from time. Amendments are managed as described in General Provisions.

      • 12.3 Assignment. You may not assign this Agreement or any of its rights under this Agreement without the prior written consent of The Company and any attempted assignment without such consent shall be void.

      • 12.4 Export Compliance. You agree to comply with all applicable laws and regulations, including laws, regulations, orders or other restrictions on export, re-export or redistribution of software.

      • 12.5 Indemnification. You agree to defend, indemnify, and hold harmless the Company from and against any lawsuits, claims, losses, damages, fines and expenses (including attorneys' fees and costs) arising out of your use of the Software or breach of this Agreement.

      • 12.6 Attorneys' Fees and Costs. The prevailing party in any action to enforce this Agreement will be entitled to recover its attorneys' fees and costs in connection with such action.

      • 12.7 Severability. If any provision of this Agreement is held by a court of competent jurisdiction to be invalid, illegal, or unenforceable, the remainder of this Agreement will remain in full force and effect.

      • 12.8 Waiver. Failure or neglect by either party to enforce at any time any of the provisions of this license Agreement shall not be construed or deemed to be a waiver of that party's rights under this Agreement.

      • 12.9 Audit. The Company may, at its expense, appoint its own personnel or an independent third party to audit the numbers of installations of the Software in use by you. Any such audit shall be conducted upon thirty (30) days prior notice, during regular business hours and shall not unreasonably interfere with your business activities.

      • 12.10 Headings. The headings of sections and paragraphs of this Agreement are for convenience of reference only and are not intended to restrict, affect or be of any weight in the interpretation or construction of the provisions of such sections or paragraphs.

      "},{"location":"eula.html#13-contact-information","title":"13. Contact Information","text":"
      • 13.1 If you have any questions about this EULA, or if you want to contact the Company for any reason, please direct correspondence to sales@stakater.com.
      "},{"location":"troubleshooting.html","title":"Troubleshooting Guide","text":""},{"location":"troubleshooting.html#operatorhub-upgrade-error","title":"OperatorHub Upgrade Error","text":""},{"location":"troubleshooting.html#operator-is-stuck-in-upgrade-if-upgrade-approval-is-set-to-automatic","title":"Operator is stuck in upgrade if upgrade approval is set to Automatic","text":""},{"location":"troubleshooting.html#problem","title":"Problem","text":"

      If operator upgrade is set to Automatic Approval on OperatorHub, there may be scenarios where it gets blocked.

      "},{"location":"troubleshooting.html#resolution","title":"Resolution","text":"

      Information

      If upgrade approval is set to manual, and you want to skip upgrade of a specific version, then delete the InstallPlan created for that specific version. Operator Lifecycle Manager (OLM) will create the latest available InstallPlan which can be approved then.\n

      As OLM does not allow to upgrade or downgrade from a version stuck because of error, the only possible fix is to uninstall the operator from the cluster. When the operator is uninstalled it removes all of its resources i.e., ClusterRoles, ClusterRoleBindings, and Deployments etc., except Custom Resource Definitions (CRDs), so none of the Custom Resources (CRs), Tenants, Templates etc., will be removed from the cluster. If any CRD has a conversion webhook defined then that webhook should be removed before installing the stable version of the operator. This can be achieved via removing the .spec.conversion block from the CRD schema.

      As an example, if you have installed v0.8.0 of Multi Tenant Operator on your cluster, then it'll stuck in an error error validating existing CRs against new CRD's schema for \"integrationconfigs.tenantoperator.stakater.com\": error validating custom resource against new schema for IntegrationConfig multi-tenant-operator/tenant-operator-config: [].spec.tenantRoles: Required value. To resolve this issue, you'll first uninstall the MTO from the cluster. Once you uninstall the MTO, check Tenant CRD which will have a conversion block, which needs to be removed. After removing the conversion block from the Tenant CRD, install the latest available version of MTO from OperatorHub.

      "},{"location":"troubleshooting.html#permission-issues","title":"Permission Issues","text":""},{"location":"troubleshooting.html#vault-user-permissions-are-not-updated-if-the-user-is-added-to-a-tenant-and-the-user-does-not-exist-in-rhsso","title":"Vault user permissions are not updated if the user is added to a Tenant, and the user does not exist in RHSSO","text":""},{"location":"troubleshooting.html#problem_1","title":"Problem","text":"

      If a user is added to tenant resource, and the user does not exist in RHSSO, then RHSSO is not updated with the user's Vault permission.

      "},{"location":"troubleshooting.html#reproduction-steps","title":"Reproduction steps","text":"
      1. Add a new user to Tenant CR
      2. Attempt to log in to Vault with the added user
      3. Vault denies that the user exists, and signs the user up via RHSSO. User is now created on RHSSO (you may check for the user on RHSSO).
      "},{"location":"troubleshooting.html#resolution_1","title":"Resolution","text":"

      If the user does not exist in RHSSO, then MTO does not create the tenant access for Vault in RHSSO.

      The user now needs to go to Vault, and sign up using OIDC. Then the user needs to wait for MTO to reconcile the updated tenant (reconciliation period is currently 1 hour). After reconciliation, MTO will add relevant access for the user in RHSSO.

      If the user needs to be added immediately and it is not feasible to wait for next MTO reconciliation, then: add a label or annotation to the user, or restart the Tenant controller pod to force immediate reconciliation.

      "},{"location":"troubleshooting.html#pod-creation-error","title":"Pod Creation Error","text":""},{"location":"troubleshooting.html#q-errors-in-replicaset-events-about-pods-not-being-able-to-schedule-on-openshift-because-scc-annotation-is-not-found","title":"Q. Errors in ReplicaSet Events about pods not being able to schedule on OpenShift because scc annotation is not found","text":"
      unable to find annotation openshift.io/sa.scc.uid-range\n

      Answer. OpenShift recently updated its process of handling SCC, and it's now managed by annotations like openshift.io/sa.scc.uid-range on the namespaces. Absence of them wont let pods schedule. The fix for the above error is to make sure ServiceAccount system:serviceaccount:openshift-infra. regex is always mentioned in Privileged.serviceAccounts section of IntegrationConfig. This regex will allow operations from all ServiceAccounts present in openshift-infra namespace. More info at Privileged Service Accounts

      "},{"location":"troubleshooting.html#namespace-admission-webhook","title":"Namespace Admission Webhook","text":""},{"location":"troubleshooting.html#q-error-received-while-performing-create-update-or-delete-action-on-namespace","title":"Q. Error received while performing Create, Update or Delete action on Namespace","text":"
      Cannot CREATE namespace test-john without label stakater.com/tenant\n

      Answer. Error occurs when a user is trying to perform create, update, delete action on a namespace without the required stakater.com/tenant label. This label is used by the operator to see that authorized users can perform that action on the namespace. Just add the label with the tenant name so that MTO knows which tenant the namespace belongs to, and who is authorized to perform create/update/delete operations. For more details please refer to Namespace use-case.

      "},{"location":"troubleshooting.html#q-error-received-while-performing-create-update-or-delete-action-on-openshift-project","title":"Q. Error received while performing Create, Update or Delete action on OpenShift Project","text":"
      Cannot CREATE namespace testing without label stakater.com/tenant. User: system:serviceaccount:openshift-apiserver:openshift-apiserver-sa\n

      Answer. This error occurs because we don't allow Tenant members to do operations on OpenShift Project, whenever an operation is done on a project, openshift-apiserver-sa tries to do the same request onto a namespace. That's why the user sees openshift-apiserver-sa Service Account instead of its own user in the error message.

      The fix is to try the same operation on the namespace manifest instead.

      "},{"location":"troubleshooting.html#q-error-received-while-doing-kubectl-apply-f-namespaceyaml","title":"Q. Error received while doing kubectl apply -f namespace.yaml","text":"
      Error from server (Forbidden): error when retrieving current configuration of:\nResource: \"/v1, Resource=namespaces\", GroupVersionKind: \"/v1, Kind=Namespace\"\nName: \"ns1\", Namespace: \"\"\nfrom server for: \"namespace.yaml\": namespaces \"ns1\" is forbidden: User \"muneeb\" cannot get resource \"namespaces\" in API group \"\" in the namespace \"ns1\"\n

      Answer. Tenant members will not be able to use kubectl apply because apply first gets all the instances of that resource, in this case namespaces, and then does the required operation on the selected resource. To maintain tenancy, tenant members do not the access to get or list all the namespaces.

      The fix is to create namespaces with kubectl create instead.

      "},{"location":"troubleshooting.html#mto-argocd-integration","title":"MTO - ArgoCD Integration","text":""},{"location":"troubleshooting.html#q-how-do-i-deploy-cluster-scoped-resource-via-the-argocd-integration","title":"Q. How do I deploy cluster-scoped resource via the ArgoCD integration?","text":"

      Answer. Multi-Tenant Operator's ArgoCD Integration allows configuration of which cluster-scoped resources can be deployed, both globally and on a per-tenant basis. For a global allow-list that applies to all tenants, you can add both resource group and kind to the IntegrationConfig's spec.integrations.argocd.clusterResourceWhitelist field. Alternatively, you can set this up on a tenant level by configuring the same details within a Tenant's spec.integrations.argocd.appProject.clusterResourceWhitelist field. For more details, check out the ArgoCD integration use cases

      "},{"location":"troubleshooting.html#q-invalidspecerror-application-repo-repo-is-not-permitted-in-project-project","title":"Q. InvalidSpecError: application repo \\<repo> is not permitted in project \\<project>","text":"

      Answer. The above error can occur if the ArgoCD Application is syncing from a source that is not allowed the referenced AppProject. To solve this, verify that you have referred to the correct project in the given ArgoCD Application, and that the repoURL used for the Application's source is valid. If the error still appears, you can add the URL to the relevant Tenant's spec.integrations.argocd.sourceRepos array.

      "},{"location":"troubleshooting.html#mto-opencost","title":"MTO - OpenCost","text":""},{"location":"troubleshooting.html#q-why-are-there-mto-showback-pods-failing-in-my-cluster","title":"Q. Why are there mto-showback-* pods failing in my cluster?","text":"

      Answer. The mto-showback-* pods are used to calculate the cost of the resources used by each tenant. These pods are created by the Multi-Tenant Operator and are scheduled to run every 10 minutes. If the pods are failing, it is likely that the operator's necessary to calculate cost are not present in the cluster. To solve this, you can navigate to Operators -> Installed Operators in the OpenShift console and check if the MTO-OpenCost and MTO-Prometheus operators are installed. If they are in a pending state, you can manually approve them to install them in the cluster.

      "},{"location":"crds-api-reference/extensions.html","title":"Extensions","text":"

      Extensions in MTO enhance its functionality by allowing integration with external services. Currently, MTO supports integration with ArgoCD, enabling you to synchronize your repositories and configure AppProjects directly through MTO. Future updates will include support for additional integrations.

      "},{"location":"crds-api-reference/extensions.html#configuring-argocd-integration","title":"Configuring ArgoCD Integration","text":"

      Let us take a look at how you can create an Extension CR and integrate ArgoCD with MTO.

      Before you create an Extension CR, you need to modify the Integration Config resource and add the ArgoCD configuration.

        integrations:\n    argocd:\n      clusterResourceWhitelist:\n        - group: tronador.stakater.com\n          kind: EnvironmentProvisioner\n      namespaceResourceBlacklist:\n        - group: ''\n          kind: ResourceQuota\n      namespace: openshift-operators\n

      The above configuration will allow the EnvironmentProvisioner CRD and blacklist the ResourceQuota resource. Also note that the namespace field is mandatory and should be set to the namespace where the ArgoCD is deployed.

      Every Extension CR is associated with a specific Tenant. Here's an example of an Extension CR that is associated with a Tenant named tenant-sample:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Extensions\nmetadata:\n  name: extensions-sample\nspec:\n  tenantName: tenant-sample\n  argoCD:\n    onDeletePurgeAppProject: true\n    appProject:\n      sourceRepos:\n        - \"github.com/stakater/repo\"\n      clusterResourceWhitelist:\n        - group: \"\"\n          kind: \"Pod\"\n      namespaceResourceBlacklist:\n        - group: \"v1\"\n          kind: \"ConfigMap\"\n

      The above CR creates an Extension for the Tenant named tenant-sample with the following configurations:

      • onDeletePurgeAppProject: If set to true, the AppProject will be deleted when the Extension is deleted.
      • sourceRepos: List of repositories to sync with ArgoCD.
      • appProject: Configuration for the AppProject.
        • clusterResourceWhitelist: List of cluster-scoped resources to sync.
        • namespaceResourceBlacklist: List of namespace-scoped resources to ignore.

      In the backend, MTO will create an ArgoCD AppProject with the specified configurations.

      "},{"location":"crds-api-reference/integration-config.html","title":"Integration Config","text":"

      IntegrationConfig is used to configure settings of multi-tenancy for Multi Tenant Operator.

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  components:\n    console: true\n    showback: true\n    ingress:\n      ingressClassName: 'nginx'\n      keycloak:\n        host: tenant-operator-keycloak.apps.mycluster-ams.abcdef.cloud\n        tlsSecretName: tenant-operator-tls\n      console:\n        host: tenant-operator-console.apps.mycluster-ams.abcdef.cloud\n        tlsSecretName: tenant-operator-tls\n      gateway:\n        host: tenant-operator-gateway.apps.mycluster-ams.abcdef.cloud\n        tlsSecretName: tenant-operator-tls\n      customPricingModel:\n        CPU: \"0.031611\"\n        spotCPU: \"0.006655\"\n        RAM: \"0.004237\"\n        spotRAM: \"0.000892\"\n        GPU: \"0.95\"\n        storage: \"0.00005479452\"\n        zoneNetworkEgress: \"0.01\"\n        regionNetworkEgress: \"0.01\"\n        internetNetworkEgress: \"0.12\"\n  accessControl:\n    rbac:\n      tenantRoles:\n        default:\n          owner:\n            clusterRoles:\n              - admin\n          editor:\n            clusterRoles:\n              - edit\n          viewer:\n            clusterRoles:\n              - view\n        custom:\n        - labelSelector:\n            matchExpressions:\n            - key: stakater.com/kind\n              operator: In\n              values:\n                - build\n            matchLabels:\n              stakater.com/kind: dev\n          owner:\n            clusterRoles:\n              - custom-owner\n          editor:\n            clusterRoles:\n              - custom-editor\n          viewer:\n            clusterRoles:\n              - custom-view\n    namespaceAccessPolicy:\n      deny:\n        privilegedNamespaces:\n          users:\n            - system:serviceaccount:openshift-argocd:argocd-application-controller\n            - adam@stakater.com\n          groups:\n            - cluster-admins\n    privileged:\n      namespaces:\n        - ^default$\n        - ^openshift.*\n        - ^kube.*\n      serviceAccounts:\n        - ^system:serviceaccount:openshift.*\n        - ^system:serviceaccount:kube.*\n      users:\n        - ''\n      groups:\n        - cluster-admins\n  metadata:\n    groups:\n      labels:\n        role: customer-reader\n      annotations: \n        openshift.io/node-selector: node-role.kubernetes.io/worker=\n    namespaces:\n      labels:\n        stakater.com/workload-monitoring: \"true\"\n      annotations:\n        openshift.io/node-selector: node-role.kubernetes.io/worker=\n    sandboxes:\n      labels:\n        stakater.com/kind: sandbox\n      annotations:\n        openshift.io/node-selector: node-role.kubernetes.io/worker=\n  integrations:\n    keycloak:\n      realm: mto\n      address: https://keycloak.apps.prod.abcdefghi.kubeapp.cloud\n      clientName: mto-console\n    argocd:\n      clusterResourceWhitelist:\n        - group: tronador.stakater.com\n          kind: EnvironmentProvisioner\n      namespaceResourceBlacklist:\n        - group: '' # all groups\n          kind: ResourceQuota\n      namespace: openshift-operators\n    vault:\n      enabled: true\n      authMethod: kubernetes      #enum: {kubernetes:default, token}\n      accessInfo: \n        accessorPath: oidc/\n        address: https://vault.apps.prod.abcdefghi.kubeapp.cloud/\n        roleName: mto\n        secretRef:       \n          name: ''\n          namespace: ''\n      config: \n        ssoClient: vault\n

      Following are the different components that can be used to configure multi-tenancy in a cluster via Multi Tenant Operator.

      "},{"location":"crds-api-reference/integration-config.html#components","title":"Components","text":"
        components:\n    console: true\n    showback: true\n    ingress:\n      ingressClassName: nginx\n      keycloak:\n        host: tenant-operator-keycloak.apps.mycluster-ams.abcdef.cloud\n        tlsSecretName: tenant-operator-tls\n      console:\n        host: tenant-operator-console.apps.mycluster-ams.abcdef.cloud\n        tlsSecretName: tenant-operator-tls\n      gateway:\n        host: tenant-operator-gateway.apps.mycluster-ams.abcdef.cloud\n        tlsSecretName: tenant-operator-tls\n
      • components.console: Enables or disables the console GUI for MTO.
      • components.showback: Enables or disables the showback feature on the console.
      • components.ingress: Configures the ingress settings for various components:
        • ingressClassName: Ingress class to be used for the ingress.
        • console: Settings for the console's ingress.
          • host: hostname for the console's ingress.
          • tlsSecretName: Name of the secret containing the TLS certificate and key for the console's ingress.
        • gateway: Settings for the gateway's ingress.
          • host: hostname for the gateway's ingress.
          • tlsSecretName: Name of the secret containing the TLS certificate and key for the gateway's ingress.
        • keycloak: Settings for the Keycloak's ingress.
          • host: hostname for the Keycloak's ingress.
          • tlsSecretName: Name of the secret containing the TLS certificate and key for the Keycloak's ingress.

      Here's an example of how to generate the secrets required to configure MTO:

      TLS Secret for Ingress:

      Create a TLS secret containing your SSL/TLS certificate and key for secure communication. This secret will be used for the Console, Gateway, and Keycloak ingresses.

      kubectl -n multi-tenant-operator create secret tls <tls-secret-name> --key=<path-to-key.pem> --cert=<path-to-cert.pem>\n

      Integration config will be managing the following resources required for console GUI:

      • MTO Postgresql resources.
      • MTO Prometheus resources.
      • MTO Opencost resources.
      • MTO Console, Gateway, Keycloak resources.
      • Showback cronjob.

      Details on console GUI and showback can be found here

      "},{"location":"crds-api-reference/integration-config.html#access-control","title":"Access Control","text":"
      accessControl:\n  rbac:\n    tenantRoles:\n      default:\n        owner:\n          clusterRoles:\n            - admin\n        editor:\n          clusterRoles:\n            - edit\n        viewer:\n          clusterRoles:\n            - view\n      custom:\n      - labelSelector:\n          matchExpressions:\n          - key: stakater.com/kind\n            operator: In\n            values:\n              - build\n          matchLabels:\n            stakater.com/kind: dev\n        owner:\n          clusterRoles:\n            - custom-owner\n        editor:\n          clusterRoles:\n            - custom-editor\n        viewer:\n          clusterRoles:\n            - custom-view\n  namespaceAccessPolicy:\n    deny:\n      privilegedNamespaces:\n        users:\n          - system:serviceaccount:openshift-argocd:argocd-application-controller\n          - adam@stakater.com\n          groups:\n            - cluster-admins\n  privileged:\n    namespaces:\n      - ^default$\n      - ^openshift.*\n      - ^kube.*\n    serviceAccounts:\n      - ^system:serviceaccount:openshift.*\n      - ^system:serviceaccount:kube.*\n    users:\n      - ''\n    groups:\n      - cluster-admins\n
      "},{"location":"crds-api-reference/integration-config.html#rbac","title":"RBAC","text":"

      RBAC is used to configure the roles that will be applied to each Tenant namespace. The field allows optional custom roles, that are then used to create RoleBindings for namespaces that match a labelSelector.

      "},{"location":"crds-api-reference/integration-config.html#tenantroles","title":"TenantRoles","text":"

      TenantRoles are required within the IntegrationConfig, as they are used for defining what roles will be applied to each Tenant namespace. The field allows optional custom roles, that are then used to create RoleBindings for namespaces that match a labelSelector.

      \u26a0\ufe0f If you do not configure roles in any way, then the default OpenShift roles of owner, edit, and view will apply to Tenant members. Their details can be found here

      rbac:\n  tenantRoles:\n    default:\n      owner:\n        clusterRoles:\n          - admin\n      editor:\n        clusterRoles:\n          - edit\n      viewer:\n        clusterRoles:\n          - view\n    custom:\n    - labelSelector:\n        matchExpressions:\n        - key: stakater.com/kind\n          operator: In\n          values:\n            - build\n        matchLabels:\n          stakater.com/kind: dev\n      owner:\n        clusterRoles:\n          - custom-owner\n      editor:\n        clusterRoles:\n          - custom-editor\n      viewer:\n        clusterRoles:\n          - custom-view\n
      "},{"location":"crds-api-reference/integration-config.html#default","title":"Default","text":"

      This field contains roles that will be used to create default roleBindings for each namespace that belongs to tenants. These roleBindings are only created for a namespace if that namespace isn't already matched by the custom field below it. Therefore, it is required to have at least one role mentioned within each of its three subfields: owner, editor, and viewer. These 3 subfields also correspond to the member fields of the Tenant CR

      "},{"location":"crds-api-reference/integration-config.html#custom","title":"Custom","text":"

      An array of custom roles. Similar to the default field, you can mention roles within this field as well. However, the custom roles also require the use of a labelSelector for each iteration within the array. The roles mentioned here will only apply to the namespaces that are matched by the labelSelector. If a namespace is matched by 2 different labelSelectors, then both roles will apply to it. Additionally, roles can be skipped within the labelSelector. These missing roles are then inherited from the default roles field . For example, if the following custom roles arrangement is used:

      custom:\n- labelSelector:\n    matchExpressions:\n    - key: stakater.com/kind\n      operator: In\n      values:\n        - build\n    matchLabels:\n      stakater.com/kind: dev\n  owner:\n    clusterRoles:\n      - custom-owner\n

      Then the editor and viewer roles will be taken from the default roles field, as that is required to have at least one role mentioned.

      "},{"location":"crds-api-reference/integration-config.html#namespace-access-policy","title":"Namespace Access Policy","text":"

      Namespace Access Policy is used to configure the namespaces that are allowed to be created by tenants. It also allows the configuration of namespaces that are ignored by MTO.

      namespaceAccessPolicy:\n  deny:\n    privilegedNamespaces:\n      groups:\n        - cluster-admins\n      users:\n        - system:serviceaccount:openshift-argocd:argocd-application-controller\n        - adam@stakater.com\n  privileged:\n    namespaces:\n      - ^default$\n      - ^openshift.*\n      - ^kube.*\n    serviceAccounts:\n      - ^system:serviceaccount:openshift.*\n      - ^system:serviceaccount:kube.*\n    users:\n      - ''\n    groups:\n      - cluster-admins\n
      "},{"location":"crds-api-reference/integration-config.html#deny","title":"Deny","text":"

      namespaceAccessPolicy.Deny: Can be used to restrict privileged users/groups CRUD operation over managed namespaces.

      "},{"location":"crds-api-reference/integration-config.html#privileged","title":"Privileged","text":""},{"location":"crds-api-reference/integration-config.html#namespaces","title":"Namespaces","text":"

      privileged.namespaces: Contains the list of namespaces ignored by MTO. MTO will not manage the namespaces in this list. Treatment for privileged namespaces does not involve further integrations or finalizers processing as with normal namespaces. Values in this list are regex patterns.

      For example:

      • To ignore the default namespace, we can specify ^default$
      • To ignore all namespaces starting with the openshift- prefix, we can specify ^openshift-.*.
      • To ignore any namespace containing stakater in its name, we can specify ^stakater.. (A constant word given as a regex pattern will match any namespace containing that word.)
      "},{"location":"crds-api-reference/integration-config.html#serviceaccounts","title":"ServiceAccounts","text":"

      privileged.serviceAccounts: Contains the list of ServiceAccounts ignored by MTO. MTO will not manage the ServiceAccounts in this list. Values in this list are regex patterns. For example, to ignore all ServiceAccounts starting with the system:serviceaccount:openshift- prefix, we can use ^system:serviceaccount:openshift-.*; and to ignore a specific service account like system:serviceaccount:builder service account we can use ^system:serviceaccount:builder$.

      Note

      stakater, stakater. and stakater.* will have the same effect. To check out the combinations, go to Regex101, select Golang, and type your expected regex and test string.

      "},{"location":"crds-api-reference/integration-config.html#users","title":"Users","text":"

      privileged.users: Contains the list of users ignored by MTO. MTO will not manage the users in this list. Values in this list are regex patterns.

      "},{"location":"crds-api-reference/integration-config.html#groups","title":"Groups","text":"

      privileged.groups: Contains names of the groups that are allowed to perform CRUD operations on namespaces present on the cluster. Users in the specified group(s) will be able to perform these operations without MTO getting in their way. MTO does not interfere even with the deletion of privilegedNamespaces.

      Note

      User kube:admin is bypassed by default to perform operations as a cluster admin, this includes operations on all the namespaces.

      \u26a0\ufe0f If you want to use a more complex regex pattern (for the privileged.namespaces or privileged.serviceAccounts field), it is recommended that you test the regex pattern first - either locally or using a platform such as https://regex101.com/.

      "},{"location":"crds-api-reference/integration-config.html#metadata","title":"Metadata","text":"
      metadata:\n  groups:\n    labels:\n      role: customer-reader\n    annotations: {}\n  namespaces:\n    labels:\n      stakater.com/workload-monitoring: \"true\"\n    annotations:\n      openshift.io/node-selector: node-role.kubernetes.io/worker=\n  sandboxes:\n    labels:\n      stakater.com/kind: sandbox\n    annotations: {}\n
      "},{"location":"crds-api-reference/integration-config.html#namespaces-group-and-sandbox","title":"Namespaces, group and sandbox","text":"

      We can use the metadata.namespaces, metadata.group and metadata.sandbox fields to automatically add labels and annotations to the Namespaces and Groups managed via MTO.

      If we want to add default labels/annotations to sandbox namespaces of tenants than we just simply add them in metadata.namespaces.labels/metadata.namespaces.annotations respectively.

      Whenever a project is made it will have the labels and annotations as mentioned above.

      kind: Project\napiVersion: project.openshift.io/v1\nmetadata:\n  name: bluesky-build\n  annotations:\n    openshift.io/node-selector: node-role.kubernetes.io/worker=\n  labels:\n    workload-monitoring: 'true'\n    stakater.com/tenant: bluesky\nspec:\n  finalizers:\n    - kubernetes\nstatus:\n  phase: Active\n
      kind: Group\napiVersion: user.openshift.io/v1\nmetadata:\n  name: bluesky-owner-group\n  labels:\n    role: customer-reader\nusers:\n  - andrew@stakater.com\n
      "},{"location":"crds-api-reference/integration-config.html#integrations","title":"Integrations","text":"

      Integrations are used to configure the integrations that MTO has with other tools. Currently, MTO supports the following integrations:

      integrations:\n  keycloak:\n    realm: mto\n    address: https://keycloak.apps.prod.abcdefghi.kubeapp.cloud\n    clientName: mto-console\n  argocd:\n    clusterResourceWhitelist:\n      - group: tronador.stakater.com\n        kind: EnvironmentProvisioner\n    namespaceResourceBlacklist:\n      - group: '' # all groups\n        kind: ResourceQuota\n    namespace: openshift-operators\n  vault:\n    enabled: true\n    authMethod: kubernetes      #enum: {kubernetes:default, Token}\n    accessInfo: \n      accessorPath: oidc/\n      address: https://vault.apps.prod.abcdefghi.kubeapp.cloud/\n      roleName: mto\n      secretRef:       \n        name: ''\n        namespace: ''\n    config: \n      ssoClient: vault\n
      "},{"location":"crds-api-reference/integration-config.html#keycloak","title":"Keycloak","text":"

      Keycloak is an open-source Identity and Access Management solution aimed at modern applications and services. It makes it easy to secure applications and services with little to no code.

      If a Keycloak instance is already set up within your cluster, configure it for MTO by enabling the following configuration:

      keycloak:\n  realm: mto\n  address: https://keycloak.apps.prod.abcdefghi.kubeapp.cloud/\n  clientName: mto-console\n
      • keycloak.realm: The realm in Keycloak where the client is configured.
      • keycloak.address: The address of the Keycloak instance.
      • keycloak.clientName: The name of the client in Keycloak.

      For more details around enabling Keycloak in MTO, visit here

      "},{"location":"crds-api-reference/integration-config.html#argocd","title":"ArgoCD","text":"

      ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes. It follows the GitOps pattern of using Git repositories as the source of truth for defining the desired application state. ArgoCD uses Kubernetes manifests and configures the applications on the cluster.

      If argocd is configured on a cluster, then ArgoCD configuration can be enabled.

      argocd:\n  enabled: bool\n  clusterResourceWhitelist:\n    - group: tronador.stakater.com\n      kind: EnvironmentProvisioner\n  namespaceResourceBlacklist:\n    - group: '' # all groups\n      kind: ResourceQuota\n  namespace: openshift-operators\n
      • argocd.clusterResourceWhitelist allows ArgoCD to sync the listed cluster scoped resources from your GitOps repo.
      • argocd.namespaceResourceBlacklist prevents ArgoCD from syncing the listed resources from your GitOps repo.
      • argocd.namespace is an optional field used to specify the namespace where ArgoCD Applications and AppProjects are deployed. The field should be populated when you want to create an ArgoCD AppProject for each tenant.
      "},{"location":"crds-api-reference/integration-config.html#vault","title":"Vault","text":"

      Vault is used to secure, store and tightly control access to tokens, passwords, certificates, encryption keys for protecting secrets and other sensitive data using a UI, CLI, or HTTP API.

      If vault is configured on a cluster, then Vault configuration can be enabled.

      vault:\n  enabled: true\n  authMethod: kubernetes      #enum: {kubernetes:default, token}\n  accessInfo: \n    accessorPath: oidc/\n    address: https://vault.apps.prod.abcdefghi.kubeapp.cloud/\n    roleName: mto\n    secretRef:\n      name: ''\n      namespace: ''\n  config: \n    ssoClient: vault\n

      If enabled, then admins have to specify the authMethod to be used for authentication. MTO supports two authentication methods:

      • kubernetes: This is the default authentication method. It uses the Kubernetes authentication method to authenticate with Vault.
      • token: This method uses a Vault token to authenticate with Vault.
      "},{"location":"crds-api-reference/integration-config.html#authmethod-kubernetes","title":"AuthMethod - Kubernetes","text":"

      If authMethod is set to kubernetes, then admins have to specify the following fields:

      • accessorPath: Accessor Path within Vault to fetch SSO accessorID
      • address: Valid Vault address reachable within cluster.
      • roleName: Vault's Kubernetes authentication role
      • sso.clientName: SSO client name.
      "},{"location":"crds-api-reference/integration-config.html#authmethod-token","title":"AuthMethod - Token","text":"

      If authMethod is set to token, then admins have to specify the following fields:

      • accessorPath: Accessor Path within Vault to fetch SSO accessorID
      • address: Valid Vault address reachable within cluster.
      • secretRef: Secret containing Vault token.
        • name: Name of the secret containing Vault token.
        • namespace: Namespace of the secret containing Vault token.

      For more details around enabling Kubernetes auth in Vault, visit here

      The role created within Vault for Kubernetes authentication should have the following permissions:

      path \"secret/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"sys/mounts\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"sys/mounts/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"managed-addons/*\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"auth/kubernetes/role/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"sys/auth\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"sys/policies/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"identity/group\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"identity/group-alias\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"identity/group/name/*\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"identity/group/id/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\n
      "},{"location":"crds-api-reference/integration-config.html#custom-pricing-model","title":"Custom Pricing Model","text":"

      You can modify IntegrationConfig to customise the default pricing model. Here is what you need at IntegrationConfig.spec.components:

      components:\n    console: true # should be enabled\n    showback: true # should be enabled\n    # add below and override any default value\n    # you can also remove the ones you do not need\n    customPricingModel:\n        CPU: \"0.031611\"\n        spotCPU: \"0.006655\"\n        RAM: \"0.004237\"\n        spotRAM: \"0.000892\"\n        GPU: \"0.95\"\n        storage: \"0.00005479452\"\n        zoneNetworkEgress: \"0.01\"\n        regionNetworkEgress: \"0.01\"\n        internetNetworkEgress: \"0.12\"\n

      After modifying your default IntegrationConfig in multi-tenant-operator namespace, a configmap named opencost-custom-pricing will be updated. You will be able to see updated pricing info in mto-console.

      "},{"location":"crds-api-reference/quota.html","title":"Quota","text":"

      Using Multi Tenant Operator, the cluster-admin can set and enforce cluster resource quotas and limit ranges for tenants.

      "},{"location":"crds-api-reference/quota.html#assigning-resource-quotas","title":"Assigning Resource Quotas","text":"

      Bill is a cluster admin who will first create Quota CR where he sets the maximum resource limits that Anna's tenant will have. Here limitrange is an optional field, cluster admin can skip it if not needed.

      kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta1\nkind: Quota\nmetadata:\n  name: small\nspec:\n  resourcequota:\n    hard:\n      requests.cpu: '5'\n      requests.memory: '5Gi'\n      configmaps: \"10\"\n      secrets: \"10\"\n      services: \"10\"\n      services.loadbalancers: \"2\"\n  limitrange:\n    limits:\n      - type: \"Pod\"\n        max:\n          cpu: \"2\"\n          memory: \"1Gi\"\n        min:\n          cpu: \"200m\"\n          memory: \"100Mi\"\nEOF\n

      For more details please refer to Quotas.

      kubectl get quota small\nNAME       STATE    AGE\nsmall      Active   3m\n

      Bill then proceeds to create a tenant for Anna, while also linking the newly created Quota.

      kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n  namespaces:\n    sandboxes:\n      enabled: true\nEOF\n

      Now that the quota is linked with Anna's tenant, Anna can create any resource within the values of resource quota and limit range.

      kubectl -n bluesky-production create deployment nginx --image nginx:latest --replicas 4\n

      Once the resource quota assigned to the tenant has been reached, Anna cannot create further resources.

      kubectl create pods bluesky-training\nError from server (Cannot exceed Namespace quota: please, reach out to the system administrators)\n
      "},{"location":"crds-api-reference/quota.html#limiting-persistentvolume-for-tenant","title":"Limiting PersistentVolume for Tenant","text":"

      Bill, as a cluster admin, wants to restrict the amount of storage a Tenant can use. For that he'll add the requests.storage field to quota.spec.resourcequota.hard. If Bill wants to restrict tenant bluesky to use only 50Gi of storage, he'll first create a quota with requests.storage field set to 50Gi.

      kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta1\nkind: Quota\nmetadata:\n  name: medium\nspec:\n  resourcequota:\n    hard:\n      requests.cpu: '5'\n      requests.memory: '10Gi'\n      requests.storage: '50Gi'\n

      Once the quota is created, Bill will create the tenant and set the quota field to the one he created.

      kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n  namespaces:\n    sandboxes:\n      enabled: true\nEOF\n

      Now, the combined storage used by all tenant namespaces will not exceed 50Gi.

      "},{"location":"crds-api-reference/quota.html#adding-storageclass-restrictions-for-tenant","title":"Adding StorageClass Restrictions for Tenant","text":"

      Now, Bill, as a cluster admin, wants to make sure that no Tenant can provision more than a fixed amount of storage from a StorageClass. Bill can restrict that using <storage-class-name>.storageclass.storage.k8s.io/requests.storage field in quota.spec.resourcequota.hard field. If Bill wants to restrict tenant sigma to use only 20Gi of storage from storage class stakater, he'll first create a StorageClass stakater and then create the relevant Quota with stakater.storageclass.storage.k8s.io/requests.storage field set to 20Gi.

      kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta1\nkind: Quota\nmetadata:\n  name: small\nspec:\n  resourcequota:\n    hard:\n      requests.cpu: '2'\n      requests.memory: '4Gi'\n      stakater.storageclass.storage.k8s.io/requests.storage: '20Gi'\n

      Once the quota is created, Bill will create the tenant and set the quota field to the one he created.

      kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n  namespaces:\n    sandboxes:\n      enabled: true\nEOF\n

      Now, the combined storage provisioned from StorageClass stakater used by all tenant namespaces will not exceed 20Gi.

      The 20Gi limit will only be applied to StorageClass stakater. If a tenant member creates a PVC with some other StorageClass, he will not be restricted.

      Tip

      More details about Resource Quota can be found here

      "},{"location":"crds-api-reference/template-group-instance.html","title":"TemplateGroupInstance","text":"

      Cluster scoped resource:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: namespace-parameterized-restrictions-tgi\nspec:\n  template: namespace-parameterized-restrictions\n  sync: true\n  selector:\n    matchExpressions:\n    - key: stakater.com/tenant\n      operator: In\n      values:\n        - alpha\n        - beta\nparameters:\n  - name: CIDR_IP\n    value: \"172.17.0.0/16\"\n

      TemplateGroupInstance distributes a template across multiple namespaces which are selected by labelSelector.

      "},{"location":"crds-api-reference/template-instance.html","title":"TemplateInstance","text":"

      Namespace scoped resource:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateInstance\nmetadata:\n  name: networkpolicy\n  namespace: build\nspec:\n  template: networkpolicy\n  sync: true\nparameters:\n  - name: CIDR_IP\n    value: \"172.17.0.0/16\"\n

      TemplateInstance are used to keep track of resources created from Templates, which are being instantiated inside a Namespace. Generally, a TemplateInstance is created from a Template and then the TemplateInstances will not be updated when the Template changes later on. To change this behavior, it is possible to set spec.sync: true in a TemplateInstance. Setting this option, means to keep this TemplateInstance in sync with the underlying template (similar to Helm upgrade).

      "},{"location":"crds-api-reference/template.html","title":"Template","text":""},{"location":"crds-api-reference/template.html#cluster-scoped-resource","title":"Cluster scoped resource","text":"
      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: redis\nresources:\n  helm:\n    releaseName: redis\n    chart:\n      repository:\n        name: redis\n        repoUrl: https://charts.bitnami.com/bitnami\n    values: |\n      redisPort: 6379\n---\napiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: networkpolicy\nparameters:\n  - name: CIDR_IP\n    value: \"172.17.0.0/16\"\nresources:\n  manifests:\n    - kind: NetworkPolicy\n      apiVersion: networking.k8s.io/v1\n      metadata:\n        name: deny-cross-ns-traffic\n      spec:\n        podSelector:\n          matchLabels:\n            role: db\n        policyTypes:\n        - Ingress\n        - Egress\n        ingress:\n        - from:\n          - ipBlock:\n              cidr: \"${{CIDR_IP}}\"\n              except:\n              - 172.17.1.0/24\n          - namespaceSelector:\n              matchLabels:\n                project: myproject\n          - podSelector:\n              matchLabels:\n                role: frontend\n          ports:\n          - protocol: TCP\n            port: 6379\n        egress:\n        - to:\n          - ipBlock:\n              cidr: 10.0.0.0/24\n          ports:\n          - protocol: TCP\n            port: 5978\n---\napiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: resource-mapping\nresources:\n  resourceMappings:\n    secrets:\n      - name: secret-s1\n        namespace: namespace-n1\n    configMaps:\n      - name: configmap-c1\n        namespace: namespace-n2\n

      Templates are used to initialize Namespaces, share common resources across namespaces, and map secrets/configmaps from one namespace to other namespaces.

      • They either contain one or more Kubernetes manifests, a reference to secrets/configmaps, or a Helm chart.
      • They are being tracked by TemplateInstances in each Namespace they are applied to.
      • They can contain pre-defined parameters such as ${namespace}/${tenant} or user-defined ${MY_PARAMETER} that can be specified within an TemplateInstance.

      Also, you can define custom variables in Template and TemplateInstance . The parameters defined in TemplateInstance are overwritten the values defined in Template .

      Manifest Templates: The easiest option to define a Template is by specifying an array of Kubernetes manifests which should be applied when the Template is being instantiated.

      Helm Chart Templates: Instead of manifests, a Template can specify a Helm chart that will be installed (using Helm template) when the Template is being instantiated.

      Resource Mapping Templates: A template can be used to map secrets and configmaps from one tenant's namespace to another tenant's namespace, or within a tenant's namespace.

      "},{"location":"crds-api-reference/template.html#mandatory-and-optional-templates","title":"Mandatory and Optional Templates","text":"

      Templates can either be mandatory or optional. By default, all Templates are optional. Cluster Admins can make Templates mandatory by adding them to the spec.templateInstances array within the Tenant configuration. All Templates listed in spec.templateInstances will always be instantiated within every Namespace that is created for the respective Tenant.

      "},{"location":"crds-api-reference/tenant.html","title":"Tenant","text":"

      A minimal Tenant definition requires only a quota field, essential for limiting resource consumption:

      apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: alpha\nspec:\n  quota: small\n

      For a more comprehensive setup, a detailed Tenant definition includes various configurations:

      apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: tenant-sample\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - kubeadmin\n      groups:\n        - admin-group\n    editors:\n      users:\n        - devuser1\n        - devuser2\n      groups:\n        - dev-group\n    viewers:\n      users:\n        - viewuser\n      groups:\n        - view-group\n  hibernation:\n  # UTC time\n    sleepSchedule: \"20 * * * *\"\n    wakeSchedule: \"40 * * * *\"        \n  namespaces:\n    sandboxes:\n      enabled: true\n      private: true\n    withoutTenantPrefix:\n      - analytics\n      - marketing\n    withTenantPrefix:\n      - dev\n      - staging\n    onDeletePurgeNamespaces: true\n    metadata:\n      common:\n        labels:\n          common-label: common-value\n        annotations:\n          common-annotation: common-value\n      sandbox:\n        labels:\n          sandbox-label: sandbox-value\n        annotations:\n          sandbox-annotation: sandbox-value\n      specific:\n        - namespaces:\n            - tenant-sample-dev\n          labels:\n            specific-label: specific-dev-value\n          annotations:\n            specific-annotation: specific-dev-value\n  desc: \"This is a sample tenant setup for the v1beta3 version.\"\n
      "},{"location":"crds-api-reference/tenant.html#access-control","title":"Access Control","text":"

      Structured access control is critical for managing roles and permissions within a tenant effectively. It divides users into three categories, each with customizable privileges. This design enables precise role-based access management.

      These roles are obtained from IntegrationConfig's TenantRoles field.

      • Owners: Have full administrative rights, including resource management and namespace creation. Their roles are crucial for high-level management tasks.
      • Editors: Granted permissions to modify resources, enabling them to support day-to-day operations without full administrative access.
      • Viewers: Provide read-only access, suitable for oversight and auditing without the ability to alter resources.

      Users and groups are linked to these roles by specifying their usernames or group names in the respective fields under owners, editors, and viewers.

      "},{"location":"crds-api-reference/tenant.html#quota","title":"Quota","text":"

      The quota field sets the resource limits for the tenant, such as CPU and memory usage, to prevent any single tenant from consuming a disproportionate amount of resources. This mechanism ensures efficient resource allocation and fosters fair usage practices across all tenants.

      For more information on quotas, please refer here.

      "},{"location":"crds-api-reference/tenant.html#namespaces","title":"Namespaces","text":"

      Controls the creation and management of namespaces within the tenant:

      • sandboxes:

        • When enabled, sandbox namespaces are created with the following naming convention - {TenantName}-{UserName}-sandbox.
        • In case of groups, the sandbox namespaces will be created for each member of the group.
        • Setting private to true will make the sandboxes visible only to the user they belong to. By default, sandbox namespaces are visible to all tenant members.
      • withoutTenantPrefix: Lists the namespaces to be created without automatically prefixing them with the tenant name, useful for shared or common resources.

      • withTenantPrefix: Namespaces listed here will be prefixed with the tenant name, ensuring easy identification and isolation.
      • onDeletePurgeNamespaces: Determines whether namespaces associated with the tenant should be deleted upon the tenant's deletion, enabling clean up and resource freeing.
      • metadata: Configures metadata like labels and annotations that are applied to namespaces managed by the tenant:
        • common: Applies specified labels and annotations across all namespaces within the tenant, ensuring consistent metadata for resources and workloads.
        • sandbox: Special metadata for sandbox namespaces, which can include templated annotations or labels for dynamic information.
          • We also support the use of a templating mechanism within annotations, specifically allowing the inclusion of the tenant's username through the placeholder {{ TENANT.USERNAME }}. This template can be utilized to dynamically insert the tenant's username value into annotations, for example, as username: {{ TENANT.USERNAME }}.
        • specific: Allows applying unique labels and annotations to specified tenant namespaces, enabling custom configurations for particular workloads or environments.
      "},{"location":"crds-api-reference/tenant.html#hibernation","title":"Hibernation","text":"

      hibernation allows for the scheduling of inactive periods for namespaces associated with the tenant, effectively putting them into a \"sleep\" mode. This capability is designed to conserve resources during known periods of inactivity.

      • Configuration for this feature involves two key fields, sleepSchedule and wakeSchedule, both of which accept strings formatted according to cron syntax.
      • These schedules dictate when the namespaces will automatically transition into and out of hibernation, aligning resource usage with actual operational needs.
      "},{"location":"crds-api-reference/tenant.html#description","title":"Description","text":"

      desc provides a human-readable description of the tenant, aiding in documentation and at-a-glance understanding of the tenant's purpose and configuration.

      \u26a0\ufe0f If same label or annotation key is being applied using different methods provided, then the highest precedence will be given to namespaces.metadata.specific followed by namespaces.metadata.common and in the end would be the ones applied from openshift.project.labels/openshift.project.annotations in IntegrationConfig

      "},{"location":"explanation/console.html","title":"MTO Console","text":""},{"location":"explanation/console.html#introduction","title":"Introduction","text":"

      The Multi Tenant Operator (MTO) Console is a comprehensive user interface designed for both administrators and tenant users to manage multi-tenant environments. The MTO Console simplifies the complexity involved in handling various aspects of tenants and their related resources.

      "},{"location":"explanation/console.html#dashboard-overview","title":"Dashboard Overview","text":"

      The dashboard serves as a centralized monitoring hub, offering insights into the current state of tenants, namespaces, and quotas. It is designed to provide a quick summary/snapshot of MTO resources' status. Additionally, it includes a Showback graph that presents a quick glance of the seven-day cost trends associated with the namespaces/tenants based on the logged-in user.

      By default, MTO Console will be disabled and has to be enabled by setting the below configuration in IntegrationConfig.

      components:\n    console: true\n    ingress:\n      ingressClassName: <ingress-class-name>\n      console:\n        host: tenant-operator-console.<hostname>\n        tlsSecretName: <tls-secret-name>\n      gateway:\n        host: tenant-operator-gateway.<hostname>\n        tlsSecretName: <tls-secret-name>\n      keycloak:\n        host: tenant-operator-keycloak.<hostname>\n        tlsSecretName: <tls-secret-name>\n    showback: true\n    trustedRootCert: <root-ca-secret-name>\n

      <hostname> : hostname of the cluster <ingress-class-name> : name of the ingress class <tls-secret-name> : name of the secret that contains the TLS certificate and key <root-ca-secret-name> : name of the secret that contains the root CA certificate

      Note: trustedRootCert and tls-secret-name are optional. If not provided, MTO will use the default root CA certificate and secrets respectively.

      Once the above configuration is set on the IntegrationConfig, MTO would start provisioning the required resources for MTO Console to be ready. In a few moments, you should be able to see the Console Ingress in the multi-tenant-operator namespace which gives you access to the Console.

      For more details on the configuration, please visit here.

      "},{"location":"explanation/console.html#tenants","title":"Tenants","text":"

      Here, admins have a bird's-eye view of all tenants, with the ability to delve into each one for detailed examination and management. This section is pivotal for observing the distribution and organization of tenants within the system. More information on each tenant can be accessed by clicking the view option against each tenant name.

      "},{"location":"explanation/console.html#tenantsquota","title":"Tenants/Quota","text":""},{"location":"explanation/console.html#viewing-quota-in-the-tenant-console","title":"Viewing Quota in the Tenant Console","text":"

      In this view, users can access a dedicated tab to review the quota utilization for their Tenants. Within this tab, users have the option to toggle between two different views: Aggregated Quota and Namespace Quota.

      "},{"location":"explanation/console.html#aggregated-quota-view","title":"Aggregated Quota View","text":"

      This view provides users with an overview of the combined resource allocation and usage across all namespaces within their tenant. It offers a comprehensive look at the total limits and usage of resources such as CPU, memory, and other defined quotas. Users can easily monitor and manage resource distribution across their entire tenant environment from this aggregated perspective.

      "},{"location":"explanation/console.html#namespace-quota-view","title":"Namespace Quota View","text":"

      Alternatively, users can opt to view quota settings on a per-namespace basis. This view allows users to focus specifically on the resource allocation and usage within individual namespaces. By selecting this option, users gain granular insights into the resource constraints and utilization for each namespace, facilitating more targeted management and optimization of resources at the namespace level.

      "},{"location":"explanation/console.html#tenantsutilization","title":"Tenants/Utilization","text":"

      In the Utilization tab of the tenant console, users are presented with a detailed table listing all namespaces within their tenant. This table provides essential metrics for each namespace, including CPU and memory utilization. The metrics shown include:

      • Cost: The cost associated with CPU and memory utilization.
      • Request Average: The average amount of CPU and memory resources requested.
      • Usage Average: The average amount of CPU and memory resources used.
      • Max: The maximum value between CPU and memory requests and used resources, calculated every 30 seconds and averaged over the selected running minutes.

      Users can adjust the interval window using the provided selector to customize the time frame for the displayed data. This table allows users to quickly assess resource utilization across all namespaces, facilitating efficient resource management and cost tracking.

      Upon selecting a specific namespace from the utilization table, users are directed to a detailed view that includes CPU and memory utilization graphs along with a workload table. This detailed view provides:

      • CPU and Memory Graphs: Visual representations of the namespace's CPU and memory usage over time, enabling users to identify trends and potential issues at a glance.
      • Workload Table: A comprehensive list of all workloads within the selected namespace, including pods, deployments, and stateful-sets. The table displays key metrics for each workload, including:
        • Cost: The cost associated with the workload's CPU and memory utilization.
        • Request Average: The average amount of CPU and memory resources requested by the workload.
        • Usage Average: The average amount of CPU and memory resources used by the workload.
        • Max: The maximum value between CPU and memory requests and used resources, calculated every 30 seconds and averaged over the running minutes.

      This detailed view provides users with in-depth insights into resource utilization at the workload level, enabling precise monitoring and optimization of resource allocation within the selected namespace.

      "},{"location":"explanation/console.html#namespaces","title":"Namespaces","text":"

      Users can view all the namespaces that belong to their tenant, offering a comprehensive perspective of the accessible namespaces for tenant members. This section also provides options for detailed exploration.

      "},{"location":"explanation/console.html#quotas","title":"Quotas","text":"

      MTO's Quotas are crucial for managing resource allocation. In this section, administrators can assess the quotas assigned to each tenant, ensuring a balanced distribution of resources in line with operational requirements.

      "},{"location":"explanation/console.html#templates","title":"Templates","text":"

      The Templates section acts as a repository for standardized resource deployment patterns, which can be utilized to maintain consistency and reliability across tenant environments. Few examples include provisioning specific k8s manifests, helm charts, secrets or configmaps across a set of namespaces.

      "},{"location":"explanation/console.html#showback","title":"Showback","text":"

      The Showback feature is an essential financial governance tool, providing detailed insights into the cost implications of resource usage by tenant or namespace or other filters. This facilitates a transparent cost management and internal chargeback or showback process, enabling informed decision-making regarding resource consumption and budgeting.

      "},{"location":"explanation/console.html#user-roles-and-permissions","title":"User Roles and Permissions","text":""},{"location":"explanation/console.html#administrators","title":"Administrators","text":"

      Administrators have overarching access to the console, including the ability to view all namespaces and tenants. They have exclusive access to the IntegrationConfig, allowing them to view all the settings and integrations.

      "},{"location":"explanation/console.html#tenant-users","title":"Tenant Users","text":"

      Regular tenant users can monitor and manage their allocated resources. However, they do not have access to the IntegrationConfig and cannot view resources across different tenants, ensuring data privacy and operational integrity.

      "},{"location":"explanation/console.html#live-yaml-configuration-and-graph-view","title":"Live YAML Configuration and Graph View","text":"

      In the MTO Console, each resource section is equipped with a \"View\" button, revealing the live YAML configuration for complete information on the resource. For Tenant resources, a supplementary \"Graph\" option is available, illustrating the relationships and dependencies of all resources under a Tenant. This dual-view approach empowers users with both the detailed control of YAML and the holistic oversight of the graph view.

      You can find more details on graph visualization here: Graph Visualization

      "},{"location":"explanation/console.html#caching-and-database","title":"Caching and Database","text":"

      MTO integrates a dedicated database to streamline resource management. Now, all resources managed by MTO are efficiently stored in a Postgres database, enhancing the MTO Console's ability to efficiently retrieve all the resources for optimal presentation.

      The implementation of this feature is facilitated by the Bootstrap controller, streamlining the deployment process. This controller creates the PostgreSQL Database, establishes a service for inter-pod communication, and generates a secret to ensure secure connectivity to the database.

      Furthermore, the introduction of a dedicated cache layer ensures that there is no added burden on the Kube API server when responding to MTO Console requests. This enhancement not only improves response times but also contributes to a more efficient and responsive resource management system.

      "},{"location":"explanation/console.html#authentication-and-authorization","title":"Authentication and Authorization","text":""},{"location":"explanation/console.html#keycloak-for-authentication","title":"Keycloak for Authentication","text":"

      MTO Console incorporates Keycloak, a leading authentication module, to manage user access securely and efficiently. Keycloak is provisioned automatically by our controllers, setting up a new realm, client, and a default user named mto.

      "},{"location":"explanation/console.html#benefits","title":"Benefits","text":"
      • Industry Standard: Offers robust, reliable authentication in line with industry standards.
      • Integration with Existing Systems: Enables easy linkage with existing Active Directories or SSO systems, avoiding the need for redundant user management.
      • Administrative Control: Grants administrators full authority over user access to the console, enhancing security and operational integrity.
      "},{"location":"explanation/console.html#postgresql-as-persistent-storage-for-keycloak","title":"PostgreSQL as Persistent Storage for Keycloak","text":"

      MTO Console leverages PostgreSQL as the persistent storage solution for Keycloak, enhancing the reliability and flexibility of the authentication system.

      It offers benefits such as enhanced data reliability, easy data export and import.

      "},{"location":"explanation/console.html#benefits_1","title":"Benefits","text":"
      • Persistent Data Storage: By using PostgreSQL, Keycloak's data, including realms, clients, and user information, is preserved even in the event of a pod restart. This ensures continuous availability and stability of the authentication system.
      • Data Exportability: Customers can easily export Keycloak configurations and data from the PostgreSQL database.
      • Transferability Across Environments: The exported data can be conveniently imported into another cluster or Keycloak instance, facilitating smooth transitions and backups.
      • No Data Loss: Ensures that critical authentication data is not lost during system updates or maintenance.
      • Operational Flexibility: Provides customers with greater control over their authentication data, enabling them to manage and migrate their configurations as needed.
      "},{"location":"explanation/console.html#built-in-module-for-authorization","title":"Built-in module for Authorization","text":"

      The MTO Console is equipped with an authorization module, designed to manage access rights intelligently and securely.

      "},{"location":"explanation/console.html#benefits_2","title":"Benefits","text":"
      • User and Tenant Based: Authorization decisions are made based on the user's membership in specific tenants, ensuring appropriate access control.
      • Role-Specific Access: The module considers the roles assigned to users, granting permissions accordingly to maintain operational integrity.
      • Elevated Privileges for Admins: Users identified as administrators or members of the clusterAdminGroups are granted comprehensive permissions across the console.
      • Database Caching: Authorization decisions are cached in the database, reducing reliance on the Kubernetes API server.
      • Faster, Reliable Access: This caching mechanism ensures quicker and more reliable access for users, enhancing the overall responsiveness of the MTO Console.
      "},{"location":"explanation/console.html#conclusion","title":"Conclusion","text":"

      The MTO Console is engineered to simplify complex multi-tenant management. The current iteration focuses on providing comprehensive visibility. Future updates could include direct CUD (Create/Update/Delete) capabilities from the dashboard, enhancing the console\u2019s functionality. The Showback feature remains a standout, offering critical cost tracking and analysis. The delineation of roles between administrators and tenant users ensures a secure and organized operational framework.

      "},{"location":"explanation/logs-metrics.html","title":"Metrics and Logs Documentation","text":"

      This document offers an overview of the Prometheus metrics implemented by the multi_tenant_operator controllers, along with an interpretation guide for the logs and statuses generated by these controllers. Each metric is designed to provide specific insights into the controllers' operational performance, while the log interpretation guide aids in understanding their behavior and workflow processes. Additionally, the status descriptions for custom resources provide operational snapshots. Together, these elements form a comprehensive toolkit for monitoring and enhancing the performance and health of the controllers.

      "},{"location":"explanation/logs-metrics.html#metrics-list","title":"Metrics List","text":"

      multi_tenant_operator_resources_deployed_total

      • Description: Tracks the total number of resources deployed by the operator.
      • Type: Gauge
      • Labels: kind, name, namespace
      • Usage: Helps to understand the overall workload managed by the operator.

      multi_tenant_operator_resources_deployed

      • Description: Monitors resources currently deployed by the operator.
      • Type: Gauge
      • Labels: kind, name, namespace, type
      • Usage: Useful for tracking the current state and type of resources managed by the operator.

      multi_tenant_operator_reconcile_error

      • Description: Indicates resources in an error state, broken down by resource kind, name, and namespace.
      • Type: Gauge
      • Labels: kind, name, namespace, state, errors
      • Usage: Essential for identifying and analyzing errors in resource management.

      multi_tenant_operator_reconcile_count

      • Description: Counts the number of reconciliations performed for a template group instance, categorized by name.
      • Type: Gauge
      • Labels: kind, name
      • Usage: Provides insight into the frequency of reconciliation processes.

      multi_tenant_operator_reconcile_seconds

      • Description: Represents the cumulative duration, in seconds, taken to reconcile a template group instance, categorized by instance name.
      • Type: Gauge
      • Labels: kind, name
      • Usage: Critical for assessing the time efficiency of the reconciliation process.

      multi_tenant_operator_reconcile_seconds_total

      • Description: Tracks the total duration, in seconds, for all reconciliation processes of a template group instance, categorized by instance name.
      • Type: Gauge
      • Labels: kind, name
      • Usage: Useful for understanding the overall time spent on reconciliation processes.
      "},{"location":"explanation/logs-metrics.html#custom-resource-status","title":"Custom Resource Status","text":"

      In this section, we delve into the status of various custom resources managed by our controllers. The kubectl describe command can be used to fetch the status of these resources.

      "},{"location":"explanation/logs-metrics.html#template-group-instance","title":"Template Group Instance","text":"

      Status from the templategroupinstances.tenantoperator.stakater.com custom resource:

      • Current Operational State: Provides a snapshot of the resource's current condition.
      • Conditions: Offers a detailed view of the resource's status, which includes:
        • InstallSucceeded: Indicates the success of the instance's installation.
        • Ready: Shows the readiness of the instance, with details on the last reconciliation process, its duration, and relevant messages.
        • Running: Reports on active processes like ongoing resource reconciliation.
      • Deployed Namespaces: Enumerates the namespaces where the instance has been deployed, along with their statuses and associated template manifests.
      • Manifest Hashes: Includes the Template Manifests Hash and Resource Mapping Hash, which provide versioning and change tracking for template manifests and resource mappings.
      "},{"location":"explanation/logs-metrics.html#log-interpretation-guide","title":"Log Interpretation Guide","text":""},{"location":"explanation/logs-metrics.html#template-group-instance-controller","title":"Template Group Instance Controller","text":"

      Logs from the tenant-operator-templategroupinstance-controller:

      • Reconciliation Process: Logs starting with Reconciling! mark the beginning of a reconciliation process for a TemplateGroupInstance. Subsequent actions like Creating/Updating TemplateGroupInstance and Retrieving list of namespaces Matching to TGI outline the reconciliation steps.
      • Namespace and Resource Management: Logs such as Namespaces test-namespace-1 is new or failed... and Creating/Updating resource... detail the management of Kubernetes resources in specific namespaces.
      • Worker Activities: Logs labeled [Worker X] show tasks being processed in parallel, including steps like Validating parameters, Gathering objects from manifest, and Apply manifests.
      • Reconciliation Completion: Entries like End Reconciling and Defering XXth Reconciling, with duration XXXms indicate the end of a reconciliation process and its duration, aiding in performance analysis.
      • Watcher Events: Logs from Watcher such as Delete call received for object... and Following resource is recreated... are key for tracking changes to Kubernetes objects.

      These logs are crucial for tracking the system's behavior, diagnosing issues, and comprehending the resource management workflow.

      "},{"location":"explanation/multi-tenancy-vault.html","title":"Multi-Tenancy in Vault","text":""},{"location":"explanation/multi-tenancy-vault.html#vault-multitenancy","title":"Vault Multitenancy","text":"

      HashiCorp Vault is an identity-based secret and encryption management system. Vault validates and authorizes a system's clients (users, machines, apps) before providing them access to secrets or stored sensitive data.

      "},{"location":"explanation/multi-tenancy-vault.html#vault-integration-in-multi-tenant-operator","title":"Vault integration in Multi Tenant Operator","text":""},{"location":"explanation/multi-tenancy-vault.html#service-account-auth-in-vault","title":"Service Account Auth in Vault","text":"

      MTO enables the Kubernetes auth method which can be used to authenticate with Vault using a Kubernetes Service Account Token. When enabled, for every tenant namespace, MTO automatically creates policies and roles that allow the service accounts present in those namespaces to read secrets at tenant's path in Vault. The name of the role is the same as namespace name.

      These service accounts are required to have stakater.com/vault-access: true label, so they can be authenticated with Vault via MTO.

      The Diagram shows how MTO enables ServiceAccounts to read secrets from Vault.

      "},{"location":"explanation/multi-tenancy-vault.html#user-oidc-auth-in-vault","title":"User OIDC Auth in Vault","text":"

      This requires a running RHSSO(RedHat Single Sign On) instance integrated with Vault over OIDC login method.

      MTO integration with Vault and RHSSO provides a way for users to log in to Vault where they only have access to relevant tenant paths.

      Once both integrations are set up with IntegrationConfig CR, MTO links tenant users to specific client roles named after their tenant under Vault client in RHSSO.

      After that, MTO creates specific policies in Vault for its tenant users.

      Mapping of tenant roles to Vault is shown below

      Tenant Role Vault Path Vault Capabilities Owner, Editor (tenantName)/* Create, Read, Update, Delete, List Owner, Editor sys/mounts/(tenantName)/* Create, Read, Update, Delete, List Owner, Editor managed-addons/* Read, List Viewer (tenantName)/* Read

      A simple user login workflow is shown in the diagram below.

      "},{"location":"explanation/template.html","title":"Understanding and Utilizing Template","text":""},{"location":"explanation/template.html#creating-templates","title":"Creating Templates","text":"

      Anna wants to create a Template that she can use to initialize or share common resources across namespaces (e.g. PullSecrets).

      Anna can either create a template using manifests field, covering Kubernetes or custom resources.

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-pull-secret\nresources:\n  manifests:\n    - kind: Secret\n      apiVersion: v1\n      metadata:\n        name: docker-pull-secret\n      data:\n        .dockercfg: eyAKICAiaHR0cHM6IC8vaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsgImF1dGgiOiAiYzNSaGEyRjBaWEk2VjI5M1YyaGhkRUZIY21WaGRGQmhjM04zYjNKayJ9Cn0K\n      type: kubernetes.io/dockercfg\n

      Or by using Helm Charts

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: redis\nresources:\n  helm:\n    releaseName: redis\n    chart:\n      repository:\n        name: redis\n        repoUrl: https://charts.bitnami.com/bitnami\n        version: 0.0.15\n    values: |\n      redisPort: 6379\n

      She can also use resourceMapping field to copy over secrets and configmaps from one namespace to others.

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: resource-mapping\nresources:\n  resourceMappings:\n    secrets:\n      - name: docker-secret\n        namespace: bluesky-build\n    configMaps:\n      - name: tronador-configMap\n        namespace: stakater-tronador\n

      Note: Resource mapping can be used via TGI to map resources within tenant namespaces or to some other tenant's namespace. If used with TI, the resources will only be mapped if namespaces belong to same tenant.

      "},{"location":"explanation/template.html#using-templates-with-default-parameters","title":"Using Templates with Default Parameters","text":"
      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: namespace-parameterized-restrictions\nparameters:\n  # Name of the parameter\n  - name: DEFAULT_CPU_LIMIT\n    # The default value of the parameter\n    value: \"1\"\n  - name: DEFAULT_CPU_REQUESTS\n    value: \"0.5\"\n    # If a parameter is required the template instance will need to set it\n    # required: true\n    # Make sure only values are entered for this parameter\n    validation: \"^[0-9]*\\\\.?[0-9]+$\"\nresources:\n  manifests:\n    - apiVersion: v1\n      kind: LimitRange\n      metadata:\n        name: namespace-limit-range-${namespace}\n      spec:\n        limits:\n          - default:\n              cpu: \"${{DEFAULT_CPU_LIMIT}}\"\n            defaultRequest:\n              cpu: \"${{DEFAULT_CPU_REQUESTS}}\"\n            type: Container\n

      Parameters can be used with both manifests and helm charts

      "},{"location":"explanation/templated-metadata-values.html","title":"Templated values in Labels and Annotations","text":"

      Templated values are placeholders in your configuration that get replaced with actual data when the CR is processed. Below is a list of currently supported templated values, their descriptions, and where they can be used.

      "},{"location":"explanation/templated-metadata-values.html#supported-templated-values","title":"Supported templated values","text":"
      • \"{{ TENANT.USERNAME }}\"

        • Description: The username associated with users specified in Tenant under Owners and Editors.
        • Supported in CRs:
          • Tenant: Under sandboxMetadata.labels and sandboxMetadata.annotations.
          • IntegrationConfig: Under metadata.sandboxs.labels and metadata.sandboxs.annotations.
        • Example:
          annotation:\n    che.eclipse.org/username: \"{{ TENANT.USERNAME }}\" # double quotes are required\n
      "},{"location":"how-to-guides/configuring-multitenant-network-isolation.html","title":"Configuring Multi-Tenant Isolation with Network Policy Template","text":"

      Bill is a cluster admin who wants to configure network policies to provide multi-tenant network isolation.

      First, Bill creates a template for network policies:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: tenant-network-policy\nresources:\n  manifests:\n  - apiVersion: networking.k8s.io/v1\n    kind: NetworkPolicy\n    metadata:\n      name: allow-same-namespace\n    spec:\n      podSelector: {}\n      ingress:\n      - from:\n        - podSelector: {}\n  - apiVersion: networking.k8s.io/v1\n    kind: NetworkPolicy\n    metadata:\n      name: allow-from-openshift-monitoring\n    spec:\n      ingress:\n      - from:\n        - namespaceSelector:\n            matchLabels:\n              network.openshift.io/policy-group: monitoring\n      podSelector: {}\n      policyTypes:\n      - Ingress\n  - apiVersion: networking.k8s.io/v1\n    kind: NetworkPolicy\n    metadata:\n      name: allow-from-openshift-ingress\n    spec:\n      ingress:\n      - from:\n        - namespaceSelector:\n            matchLabels:\n              network.openshift.io/policy-group: ingress\n      podSelector: {}\n      policyTypes:\n      - Ingress\n

      Once the template has been created, Bill edits the IntegrationConfig to add unique label to all tenant projects:

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  metadata:\n    namespaces:\n      labels:\n        stakater.com/workload-monitoring: \"true\"\n        tenant-network-policy: \"true\"\n      annotations:\n        openshift.io/node-selector: node-role.kubernetes.io/worker=\n    sandbox:\n      labels:\n        stakater.com/kind: sandbox\n  privileged:\n    namespaces:\n      - default\n      - ^openshift.*\n      - ^kube.*\n    serviceAccounts:\n      - ^system:serviceaccount:openshift.*\n      - ^system:serviceaccount:kube.*\n

      Bill has added a new label tenant-network-policy: \"true\" in project section of IntegrationConfig, now MTO will add that label in all tenant projects.

      Finally, Bill creates a TemplateGroupInstance which will distribute the network policies using the newly added project label and template.

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: tenant-network-policy-group\nspec:\n  template: tenant-network-policy\n  selector:\n    matchLabels:\n      tenant-network-policy: \"true\"\n  sync: true\n

      MTO will now deploy the network policies mentioned in Template to all projects matching the label selector mentioned in the TemplateGroupInstance.

      "},{"location":"how-to-guides/copying-resources.html","title":"Propagate Secrets from Parent to Descendant namespaces","text":"

      Secrets like registry credentials often need to exist in multiple Namespaces, so that Pods within different namespaces can have access to those credentials in form of secrets.

      Manually creating secrets within different namespaces could lead to challenges, such as:

      • Someone will have to create secret either manually or via GitOps each time there is a new descendant namespace that needs the secret
      • If we update the parent secret, they will have to update the secret in all descendant namespaces
      • This could be time-consuming, and a small mistake while creating or updating the secret could lead to unnecessary debugging

      With the help of Multi-Tenant Operator's Template feature we can make this secret distribution experience easy.

      For example, to copy a Secret called registry which exists in the example to new Namespaces whenever they are created, we will first create a Template which will have reference of the registry secret.

      It will also push updates to the copied Secrets and keep the propagated secrets always sync and updated with parent namespaces.

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: registry-secret\nresources:\n  resourceMappings:\n    secrets:\n      - name: registry\n        namespace: example\n

      Now using this Template we can propagate registry secret to different namespaces that have some common set of labels.

      For example, will just add one label kind: registry and all namespaces with this label will get this secret.

      For propagating it on different namespaces dynamically will have to create another resource called TemplateGroupInstance. TemplateGroupInstance will have Template and matchLabel mapping as shown below:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: registry-secret-group-instance\nspec:\n  template: registry-secret\n  selector:\n    matchLabels:\n      kind: registry\n  sync: true\n

      After reconciliation, you will be able to see those secrets in namespaces having mentioned label.

      MTO will keep injecting this secret to the new namespaces created with that label.

      kubectl get secret registry-secret -n example-ns-1\nNAME             STATE    AGE\nregistry-secret    Active   3m\n\nkubectl get secret registry-secret -n example-ns-2\nNAME             STATE    AGE\nregistry-secret    Active   3m\n
      "},{"location":"how-to-guides/custom-metrics.html","title":"Custom Metrics Support","text":"

      Multi Tenant Operator now supports custom metrics for templates, template instances and template group instances. This feature allows users to monitor the usage of templates and template instances in their cluster.

      To enable custom metrics and view them in your OpenShift cluster, you need to follow the steps below:

      • Ensure that cluster monitoring is enabled in your cluster. You can check this by going to Observe -> Metrics in the OpenShift console.
      • Navigate to Administration -> Namespaces in the OpenShift console. Select the namespace where you have installed Multi Tenant Operator.
      • Add the following label to the namespace: openshift.io/cluster-monitoring=true. This will enable cluster monitoring for the namespace.
      • To ensure that the metrics are being scraped for the namespace, navigate to Observe -> Targets in the OpenShift console. You should see the namespace in the list of targets.
      • To view the custom metrics, navigate to Observe -> Metrics in the OpenShift console. You should see the custom metrics for templates, template instances and template group instances in the list of metrics.

      Details of metrics can be found at Metrics and Logs

      "},{"location":"how-to-guides/custom-roles.html","title":"Changing the default access level for tenant owners","text":"

      This feature allows the cluster admins to change the default roles assigned to Tenant owner, editor, viewer groups.

      For example, if Bill as the cluster admin wants to reduce the privileges that tenant owners have, so they cannot create or edit Roles or bind them. As an admin of an OpenShift cluster, Bill can do this by assigning the edit role to all tenant owners. This is easily achieved by modifying the IntegrationConfig:

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  accessControl:\n    rbac:\n      tenantRoles:\n        default:\n          owner:\n            clusterRoles:\n              - edit\n          editor:\n            clusterRoles:\n              - edit\n          viewer:\n            clusterRoles:\n              - view\n

      Once all namespaces reconcile, the old admin RoleBindings should get replaced with the edit ones for each tenant owner.

      "},{"location":"how-to-guides/custom-roles.html#giving-specific-permissions-to-some-tenants","title":"Giving specific permissions to some tenants","text":"

      Bill now wants the owners of the tenants bluesky and alpha to have admin permissions over their namespaces. Custom roles feature will allow Bill to do this, by modifying the IntegrationConfig like this:

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  accessControl:\n    rbac:\n      tenantRoles:\n        default:\n          owner:\n            clusterRoles:\n              - edit\n          editor:\n            clusterRoles:\n              - edit\n          viewer:\n            clusterRoles:\n              - view\n        custom:\n        - labelSelector:\n            matchExpressions:\n            - key: stakater.com/tenant\n              operator: In\n              values:\n                - alpha\n          owner:\n            clusterRoles:\n              - admin\n        - labelSelector:\n            matchExpressions:\n            - key: stakater.com/tenant\n              operator: In\n              values:\n                - bluesky\n          owner:\n            clusterRoles:\n              - admin\n

      New Bindings will be created for the Tenant owners of bluesky and alpha, corresponding to the admin Role. Bindings for editors and viewer will be inherited from the default roles. All other Tenant owners will have an edit Role bound to them within their namespaces

      "},{"location":"how-to-guides/deploying-private-helm-charts.html","title":"Deploying Private Helm Chart to Multiple Namespaces","text":"

      Multi Tenant Operator uses its helm functionality from Template and TemplateGroupInstance to deploy private and public charts to multiple namespaces.

      "},{"location":"how-to-guides/deploying-private-helm-charts.html#deploying-helm-chart-to-namespaces-via-templategroupinstances-from-oci-registry","title":"Deploying Helm Chart to Namespaces via TemplateGroupInstances from OCI Registry","text":"

      Bill, the cluster admin, wants to deploy a helm chart from OCI registry in namespaces where certain labels exists.

      First, Bill creates a template:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: chart-deploy\nresources:\n  helm:\n    releaseName: random-release\n    chart:\n      repository:\n        name: random-chart\n        repoUrl: 'oci://ghcr.io/stakater/charts/random-chart'\n        version: 0.0.15\n        password:\n          key: password\n          name: repo-user\n          namespace: shared-ns\n        username:\n          key: username\n          name: repo-user\n          namespace: shared-ns\n

      Once the template has been created, Bill makes a TemplateGroupInstance referring to the Template he wants to deploy with MatchLabels:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: chart-deploy\nspec:\n  selector:\n    matchExpressions:\n      - key: stakater.com/kind\n        operator: In\n        values:\n          - system\n  sync: true\n  template: chart-deploy\n

      Multi Tenant Operator will pick up the credentials from the mentioned namespace to pull the chart and apply it.

      Afterward, Bill can see that manifests in the chart have been successfully created in all label matching namespaces.

      "},{"location":"how-to-guides/deploying-private-helm-charts.html#deploying-helm-chart-to-namespaces-via-templategroupinstances-from-https-registry","title":"Deploying Helm Chart to Namespaces via TemplateGroupInstances from HTTPS Registry","text":"

      Bill, the cluster admin, wants to deploy a helm chart from HTTPS registry in namespaces where certain labels exists.

      First, Bill creates a template:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: chart-deploy\nresources:\n  helm:\n    releaseName: random-release\n    chart:\n      repository:\n        name: random-chart\n        repoUrl: 'nexus-helm-url/registry'\n        version: 0.0.15\n        password:\n          key: password\n          name: repo-user\n          namespace: shared-ns\n        username:\n          key: username\n          name: repo-user\n          namespace: shared-ns\n

      Once the template has been created, Bill makes a TemplateGroupInstance referring to the Template he wants to deploy with MatchLabels:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: chart-deploy\nspec:\n  selector:\n    matchExpressions:\n      - key: stakater.com/kind\n        operator: In\n        values:\n          - system\n  sync: true\n  template: chart-deploy\n

      Multi Tenant Operator will pick up the credentials from the mentioned namespace to pull the chart and apply it.

      Afterward, Bill can see that manifests in the chart have been successfully created in all label matching namespaces.

      "},{"location":"how-to-guides/deploying-templates.html","title":"Distributing Resources in Namespaces","text":"

      Multi Tenant Operator has two Custom Resources which can cover this need using the Template CR, depending upon the conditions and preference.

      1. TemplateGroupInstance
      2. TemplateInstance
      "},{"location":"how-to-guides/deploying-templates.html#deploying-template-to-namespaces-via-templategroupinstances","title":"Deploying Template to Namespaces via TemplateGroupInstances","text":"

      Bill, the cluster admin, wants to deploy a docker pull secret in namespaces where certain labels exists.

      First, Bill creates a template:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-secret\nresources:\n  manifests:\n    - kind: Secret\n      apiVersion: v1\n      metadata:\n        name: docker-pull-secret\n      data:\n        .dockercfg: eyAKICAiaHR0cHM6IC8vaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsgImF1dGgiOiAiYzNSaGEyRjBaWEk2VjI5M1YyaGhkRUZIY21WaGRGQmhjM04zYjNKayJ9Cn0K\n      type: kubernetes.io/dockercfg\n

      Once the template has been created, Bill makes a TemplateGroupInstance referring to the Template he wants to deploy with MatchLabels:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: docker-secret-group-instance\nspec:\n  template: docker-pull-secret\n  selector:\n    matchLabels:\n      kind: build\n  sync: true\n

      Afterward, Bill can see that secrets have been successfully created in all label matching namespaces.

      kubectl get secret docker-secret -n bluesky-anna-aurora-sandbox\nNAME             STATE    AGE\ndocker-secret    Active   3m\n\nkubectl get secret docker-secret -n alpha-dave-aurora-sandbox\nNAME             STATE    AGE\ndocker-secret    Active   2m\n

      TemplateGroupInstance can also target specific tenants or all tenant namespaces under a single YAML definition.

      "},{"location":"how-to-guides/deploying-templates.html#templategroupinstance-for-multiple-tenants","title":"TemplateGroupInstance for multiple Tenants","text":"

      It can be done by using the matchExpressions field, dividing the tenant label in key and values.

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: docker-secret-group-instance\nspec:\n  template: docker-pull-secret\n  selector:\n    matchExpressions:\n    - key: stakater.com/tenant\n      operator: In\n      values:\n        - alpha\n        - beta\n  sync: true\n
      "},{"location":"how-to-guides/deploying-templates.html#templategroupinstance-for-all-tenants","title":"TemplateGroupInstance for all Tenants","text":"

      This can also be done by using the matchExpressions field, using just the tenant label key stakater.com/tenant.

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: docker-secret-group-instance\nspec:\n  template: docker-pull-secret\n  selector:\n    matchExpressions:\n    - key: stakater.com/tenant\n      operator: Exists\n  sync: true\n
      "},{"location":"how-to-guides/deploying-templates.html#deploying-template-to-a-namespace-via-templateinstance","title":"Deploying Template to a Namespace via TemplateInstance","text":"

      Anna wants to deploy a docker pull secret in her namespace.

      First Anna asks Bill, the cluster admin, to create a template of the secret for her:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-pull-secret\nresources:\n  manifests:\n    - kind: Secret\n      apiVersion: v1\n      metadata:\n        name: docker-pull-secret\n      data:\n        .dockercfg: eyAKICAiaHR0cHM6IC8vaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsgImF1dGgiOiAiYzNSaGEyRjBaWEk2VjI5M1YyaGhkRUZIY21WaGRGQmhjM04zYjNKayJ9Cn0K\n      type: kubernetes.io/dockercfg\n

      Once the template has been created, Anna creates a TemplateInstance in her namespace referring to the Template she wants to deploy:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateInstance\nmetadata:\n  name: docker-pull-secret-instance\n  namespace: bluesky-anna-aurora-sandbox\nspec:\n  template: docker-pull-secret\n  sync: true\n

      Once this is created, Anna can see that the secret has been successfully applied.

      kubectl get secret docker-secret -n bluesky-anna-aurora-sandbox\nNAME                  STATE    AGE\ndocker-pull-secret    Active   3m\n
      "},{"location":"how-to-guides/deploying-templates.html#passing-parameters-to-template-via-templateinstance-templategroupinstance","title":"Passing Parameters to Template via TemplateInstance, TemplateGroupInstance","text":"

      Anna wants to deploy a LimitRange resource to certain namespaces.

      First Anna asks Bill, the cluster admin, to create template with parameters for LimitRange for her:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: namespace-parameterized-restrictions\nparameters:\n  # Name of the parameter\n  - name: DEFAULT_CPU_LIMIT\n    # The default value of the parameter\n    value: \"1\"\n  - name: DEFAULT_CPU_REQUESTS\n    value: \"0.5\"\n    # If a parameter is required the template instance will need to set it\n    # required: true\n    # Make sure only values are entered for this parameter\n    validation: \"^[0-9]*\\\\.?[0-9]+$\"\nresources:\n  manifests:\n    - apiVersion: v1\n      kind: LimitRange\n      metadata:\n        name: namespace-limit-range-${namespace}\n      spec:\n        limits:\n          - default:\n              cpu: \"${{DEFAULT_CPU_LIMIT}}\"\n            defaultRequest:\n              cpu: \"${{DEFAULT_CPU_REQUESTS}}\"\n            type: Container\n

      Afterward, Anna creates a TemplateInstance in her namespace referring to the Template she wants to deploy:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateInstance\nmetadata:\n  name: namespace-parameterized-restrictions-instance\n  namespace: bluesky-anna-aurora-sandbox\nspec:\n  template: namespace-parameterized-restrictions\n  sync: true\nparameters:\n  - name: DEFAULT_CPU_LIMIT\n    value: \"1.5\"\n  - name: DEFAULT_CPU_REQUESTS\n    value: \"1\"\n

      If she wants to distribute the same Template over multiple namespaces, she can use TemplateGroupInstance.

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: namespace-parameterized-restrictions-tgi\nspec:\n  template: namespace-parameterized-restrictions\n  sync: true\n  selector:\n    matchExpressions:\n    - key: stakater.com/tenant\n      operator: In\n      values:\n        - alpha\n        - beta\nparameters:\n  - name: DEFAULT_CPU_LIMIT\n    value: \"1.5\"\n  - name: DEFAULT_CPU_REQUESTS\n    value: \"1\"\n
      "},{"location":"how-to-guides/distributing-secrets-using-sealed-secret-template.html","title":"Distributing Secrets Using Sealed Secrets Template","text":"

      Bill is a cluster admin who wants to provide a mechanism for distributing secrets in multiple namespaces. For this, he wants to use Sealed Secrets as the solution by adding them to MTO Template CR

      First, Bill creates a Template in which Sealed Secret is mentioned:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: tenant-sealed-secret\nresources:\n  manifests:\n  - kind: SealedSecret\n    apiVersion: bitnami.com/v1alpha1\n    metadata:\n      name: mysecret\n    spec:\n      encryptedData:\n        .dockerconfigjson: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq.....\n      template:\n        type: kubernetes.io/dockerconfigjson\n        # this is an example of labels and annotations that will be added to the output secret\n        metadata:\n          labels:\n            \"jenkins.io/credentials-type\": usernamePassword\n          annotations:\n            \"jenkins.io/credentials-description\": credentials from Kubernetes\n

      Once the template has been created, Bill has to edit the Tenant to add unique label to namespaces in which the secret has to be deployed. For this, he can use the support for common and specific labels across namespaces.

      Bill has to specify a label on namespaces in which he needs the secret. He can add it to all namespaces inside a tenant or some specific namespaces depending on the use case.

      apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: false\n    withTenantPrefix:\n      - dev\n      - build\n      - prod\n    withoutTenantPrefix: []\n    metadata:\n      specific:\n        - namespaces:\n            - bluesky-test-namespace\n          labels:\n            distribute-image-pull-secret: true\n      common:\n        labels:\n          distribute-image-pull-secret: true\n

      Bill has added support for a new label distribute-image-pull-secret: true\" for tenant projects/namespaces, now MTO will add that label depending on the used field.

      Finally, Bill creates a TemplateGroupInstance which will deploy the sealed secrets using the newly created project label and template.

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: tenant-sealed-secret\nspec:\n  template: tenant-sealed-secret\n  selector:\n    matchLabels:\n      distribute-image-pull-secret: true\n  sync: true\n

      MTO will now deploy the sealed secrets mentioned in Template to namespaces which have the mentioned label. The rest of the work to deploy secret from a sealed secret has to be done by Sealed Secrets Controller.

      "},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html","title":"Enabling Multi-Tenancy in ArgoCD","text":""},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html#argocd-integration-in-multi-tenant-operator","title":"ArgoCD integration in Multi Tenant Operator","text":"

      With the Multi-Tenant Operator (MTO), cluster administrators can configure multi-tenancy within their cluster. The integration of ArgoCD with MTO allows for the configuration of multi-tenancy in ArgoCD applications and AppProjects.

      MTO can be configured to create AppProjects for each tenant. These AppProjects enable tenants to create ArgoCD Applications that can be synced to namespaces owned by them. Cluster admins can blacklist certain namespace resources and allow specific cluster-scoped resources as needed (see the NamespaceResourceBlacklist and ClusterResourceWhitelist sections in Integration Config docs and Tenant Custom Resource docs).

      Note that ArgoCD integration in MTO is optional.

      "},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html#default-argocd-configuration","title":"Default ArgoCD configuration","text":"

      We have set a default ArgoCD configuration in Multi Tenant Operator that fulfils the following use cases:

      • Tenants can only see their ArgoCD applications in the ArgoCD frontend.
      • Tenant 'Owners' and 'Editors' have full access to their ArgoCD applications.
      • Tenants in the 'Viewers' group have read-only access to their ArgoCD applications.
      • Tenants can sync all namespace-scoped resources, except those that are blacklisted.
      • Tenants can sync only cluster-scoped resources that are allow-listed.
      • Tenant 'Owners' can configure their own GitOps source repositories at the tenant level.
      • Cluster admins can prevent specific resources from syncing via ArgoCD.
      • Cluster admins have full access to all ArgoCD applications and AppProjects.
      • ArgoCD integration is on a per-tenant level; namespace-scoped applications are synced only to tenant namespaces.
      "},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html#creating-argocd-appprojects-for-your-tenant","title":"Creating ArgoCD AppProjects for your tenant","text":"

      To ensure each tenant has their own ArgoCD AppProjects, administrators must first specify the ArgoCD namespace in the IntegrationConfig:

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  ...\n  argocd:\n    namespace: openshift-operators\n  ...\n

      Administrators then create an Extension CR associated with the tenant:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Extensions\nmetadata:\n  name: extensions-sample\nspec:\n  tenantName: tenant-sample\n  argoCD:\n    onDeletePurgeAppProject: true\n    appProject:\n      sourceRepos:\n        - \"github.com/stakater/repo\"\n      clusterResourceWhitelist:\n        - group: \"\"\n          kind: \"Pod\"\n      namespaceResourceBlacklist:\n        - group: \"v1\"\n          kind: \"ConfigMap\"\n

      This creates an AppProject for the tenant:

      oc get AppProject -A\nNAMESPACE             NAME           AGE\nopenshift-operators   tenant-sample  5d15h\n

      Example of the created AppProject:

      apiVersion: argoproj.io/v1alpha1\nkind: AppProject\nmetadata:\n  name: tenant-sample\n  namespace: openshift-operators\nspec:\n  destinations:\n    - namespace: tenant-sample-build\n      server: \"https://kubernetes.default.svc\"\n    - namespace: tenant-sample-dev\n      server: \"https://kubernetes.default.svc\"\n    - namespace: tenant-sample-stage\n      server: \"https://kubernetes.default.svc\"\n  roles:\n    - description: >-\n        Role that gives full access to all resources inside the tenant's\n        namespace to the tenant owner groups\n      groups:\n        - saap-cluster-admins\n        - stakater-team\n        - tenant-sample-owner-group\n      name: tenant-sample-owner\n      policies:\n        - \"p, proj:tenant-sample:tenant-sample-owner, *, *, tenant-sample/*, allow\"\n    - description: >-\n        Role that gives edit access to all resources inside the tenant's\n        namespace to the tenant owner group\n      groups:\n        - saap-cluster-admins\n        - stakater-team\n        - tenant-sample-edit-group\n      name: tenant-sample-edit\n      policies:\n        - \"p, proj:tenant-sample:tenant-sample-edit, *, *, tenant-sample/*, allow\"\n    - description: >-\n        Role that gives view access to all resources inside the tenant's\n        namespace to the tenant owner group\n      groups:\n        - saap-cluster-admins\n        - stakater-team\n        - tenant-sample-view-group\n      name: tenant-sample-view\n      policies:\n        - \"p, proj:tenant-sample:tenant-sample-view, *, get, tenant-sample/*, allow\"\n  sourceRepos:\n    - \"https://github.com/stakater/gitops-config\"\n

      Users belonging to the tenant group will now see only applications created by them in the ArgoCD frontend:

      Note

      For ArgoCD Multi Tenancy to work properly, any default roles or policies attached to all users must be removed.

      "},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html#preventing-argocd-from-syncing-certain-namespaced-resources","title":"Preventing ArgoCD from Syncing Certain Namespaced Resources","text":"

      To prevent tenants from syncing ResourceQuota and LimitRange resources to their namespaces, administrators can specify these resources in the blacklist section of the ArgoCD configuration in the IntegrationConfig:

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  ...\n  integrations:\n    argocd:\n      namespace: openshift-operators\n      namespaceResourceBlacklist:\n        - group: \"\"\n          kind: ResourceQuota\n        - group: \"\"\n          kind: LimitRange\n  ...\n

      This configuration ensures these resources are not synced by ArgoCD if added to any tenant's project directory in GitOps. The AppProject will include the blacklisted resources:

      apiVersion: argoproj.io/v1alpha1\nkind: AppProject\nmetadata:\n  name: tenant-sample\n  namespace: openshift-operators\nspec:\n  ...\n  namespaceResourceBlacklist:\n    - group: ''\n      kind: ResourceQuota\n    - group: ''\n      kind: LimitRange\n  ...\n
      "},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html#allowing-argocd-to-sync-certain-cluster-wide-resources","title":"Allowing ArgoCD to Sync Certain Cluster-Wide Resources","text":"

      To allow tenants to sync the Environment cluster-scoped resource, administrators can specify this resource in the allow-list section of the ArgoCD configuration in the IntegrationConfig's spec:

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  ...\n  integrations:\n    argocd:\n      namespace: openshift-operators\n      clusterResourceWhitelist:\n        - group: \"\"\n          kind: Environment\n  ...\n

      This configuration ensures these resources are synced by ArgoCD if added to any tenant's project directory in GitOps. The AppProject will include the allow-listed resources:

      apiVersion: argoproj.io/v1alpha1\nkind: AppProject\nmetadata:\n  name: tenant-sample\n  namespace: openshift-operators\nspec:\n  ...\n  clusterResourceWhitelist:\n  - group: \"\"\n    kind: Environment\n  ...\n
      "},{"location":"how-to-guides/enabling-multi-tenancy-argocd.html#overriding-namespaceresourceblacklist-andor-clusterresourcewhitelist-per-tenant","title":"Overriding NamespaceResourceBlacklist and/or ClusterResourceWhitelist Per Tenant","text":"

      To override the namespaceResourceBlacklist and/or clusterResourceWhitelist set via Integration Config for a specific tenant, administrators can specify these in the argoCD section of the Extension CR:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Extensions\nmetadata:\n  name: extensions-blue-sky\nspec:\n  tenantName: blue-sky\n  argoCD:\n    onDeletePurgeAppProject: true\n    appProject:\n      sourceRepos:\n        - \"github.com/stakater/repo\"\n      clusterResourceWhitelist:\n        - group: \"\"\n          kind: \"Pod\"\n      namespaceResourceBlacklist:\n        - group: \"v1\"\n          kind: \"ConfigMap\"\n

      This configuration allows for tailored settings for each tenant, ensuring flexibility and control over ArgoCD resources.

      "},{"location":"how-to-guides/enabling-multi-tenancy-vault.html","title":"Configuring Vault in IntegrationConfig","text":"

      Vault is used to secure, store and tightly control access to tokens, passwords, certificates, and encryption keys for protecting secrets and other sensitive data using a UI, CLI, or HTTP API.

      To enable Vault multi-tenancy, a role has to be created in Vault under Kubernetes authentication with the following permissions:

      path \"secret/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"sys/mounts\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"sys/mounts/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"managed-addons/*\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"auth/kubernetes/role/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"sys/auth\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"sys/policies/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"identity/group\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"identity/group-alias\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\npath \"identity/group/name/*\" {\n  capabilities = [\"read\", \"list\"]\n}\npath \"identity/group/id/*\" {\n  capabilities = [\"create\", \"read\", \"update\", \"patch\", \"delete\", \"list\"]\n}\n

      If Bill (the cluster admin) has Vault configured in his cluster, then he can take benefit from MTO's integration with Vault.

      MTO automatically creates Vault secret paths for tenants, where tenant members can securely save their secrets. It also authorizes tenant members to access these secrets via OIDC.

      Bill would first have to integrate Vault with MTO by adding the details in IntegrationConfig. For more details

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  integrations:\n    vault:\n      enabled: true\n      authMethod: kubernetes\n      accessInfo: \n        accessorPath: oidc/\n        address: https://vault.apps.prod.abcdefghi.kubeapp.cloud/\n        roleName: mto\n        secretRef:       \n          name: ''\n          namespace: ''\n      config: \n        ssoClient: vault\n

      Bill then creates a tenant for Anna and John:

      apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  accessControl:\n    owners:\n      users:\n      - anna@acme.org\n    viewers:\n      users:\n      - john@acme.org\n  quota: small\n  namespaces:\n    sandboxes:\n      enabled: false\n

      Now Bill goes to Vault and sees that a path for tenant has been made under the name bluesky/kv, confirming that Tenant members with the Owner or Edit roles now have access to the tenant's Vault path.

      Now if Anna sign's in to the Vault via OIDC, she can see her tenants path and secrets. Whereas if John sign's in to the Vault via OIDC, he can't see his tenants path or secrets as he doesn't have the access required to view them.

      For more details around enabling Kubernetes auth in Vault, visit here

      "},{"location":"how-to-guides/enabling-openshift-dev-workspace.html","title":"Enabling DevWorkspace for Tenant's sandbox in OpenShift","text":""},{"location":"how-to-guides/enabling-openshift-dev-workspace.html#devworkspaces-metadata-via-multi-tenant-operator","title":"DevWorkspaces metadata via Multi Tenant Operator","text":"

      DevWorkspaces require specific metadata on a namespace for it to work in it. With Multi Tenant Operator (MTO), you can create sandbox namespaces for users of a Tenant, and then add the required metadata automatically on all sandboxes.

      "},{"location":"how-to-guides/enabling-openshift-dev-workspace.html#required-metadata-for-enabling-devworkspace-on-sandbox","title":"Required metadata for enabling DevWorkspace on sandbox","text":"
        labels:\n    app.kubernetes.io/part-of: che.eclipse.org\n    app.kubernetes.io/component: workspaces-namespace\n  annotations:\n    che.eclipse.org/username: <username>\n
      "},{"location":"how-to-guides/enabling-openshift-dev-workspace.html#automate-sandbox-metadata-for-all-tenant-users-via-tenant-cr","title":"Automate sandbox metadata for all Tenant users via Tenant CR","text":"

      With Multi Tenant Operator (MTO), you can set sandboxMetadata like below to automate metadata for all sandboxes:

      apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@acme.org\n    editors:\n      users:\n        - erik@acme.org\n    viewers:\n      users:\n        - john@acme.org\n  namespaces:\n    sandboxes:\n      enabled: true\n      private: false\n    metadata:\n      sandbox:\n        labels:\n          app.kubernetes.io/part-of: che.eclipse.org\n          app.kubernetes.io/component: workspaces-namespace\n        annotations:\n          che.eclipse.org/username: \"{{ TENANT.USERNAME }}\"\n

      It will create sandbox namespaces and also apply the sandboxMetadata for owners and editors. Notice the template {{ TENANT.USERNAME }}, it will resolve the username as value of the corresponding annotation. For more info on templated value, see here

      "},{"location":"how-to-guides/enabling-openshift-dev-workspace.html#automate-sandbox-metadata-for-all-tenant-users-via-integrationconfig-cr","title":"Automate sandbox metadata for all Tenant users via IntegrationConfig CR","text":"

      You can also automate the metadata on all sandbox namespaces by using IntegrationConfig, notice metadata.sandboxes:

      apiVersion: tenantoperator.stakater.com/v1beta1\nkind: IntegrationConfig\nmetadata:\n  name: tenant-operator-config\n  namespace: multi-tenant-operator\nspec:\n  accessControl:\n    namespaceAccessPolicy:\n      deny:\n        privilegedNamespaces: {}\n    privileged:\n      namespaces:\n      - ^default$\n      - ^openshift.*\n      - ^kube.*\n      serviceAccounts:\n      - ^system:serviceaccount:openshift.*\n      - ^system:serviceaccount:kube.*\n      - ^system:serviceaccount:stakater-actions-runner-controller:actions-runner-controller-runner-deployment$\n    rbac:\n      tenantRoles:\n        default:\n          editor:\n            clusterRoles:\n            - edit\n          owner:\n            clusterRoles:\n            - admin\n          viewer:\n            clusterRoles:\n            - view\n  components:\n    console: false\n    ingress:\n      console: {}\n      gateway: {}\n      keycloak: {}\n    showback: false\n  integrations:\n    vault:\n      accessInfo:\n        accessorPath: \"\"\n        address: \"\"\n        roleName: \"\"\n        secretRef:\n          name: \"\"\n          namespace: \"\"\n      authMethod: kubernetes\n      config:\n        ssoClient: \"\"\n      enabled: false\n  metadata:\n    groups: {}\n    namespaces: {}\n    sandboxes:\n      labels:\n        app.kubernetes.io/part-of: che.eclipse.org\n        app.kubernetes.io/component: workspaces-namespace\n      annotations:\n        che.eclipse.org/username: \"{{ TENANT.USERNAME }}\"\n

      For more info on templated value \"{{ TENANT.USERNAME }}\", see here

      "},{"location":"how-to-guides/extend-default-roles.html","title":"Extending the default access level for tenant members","text":"

      Bill as the cluster admin wants to extend the default access for tenant members. As an admin of an OpenShift Cluster, Bill can extend the admin, edit, and view ClusterRole using aggregation. Bill will first create a ClusterRole with privileges to resources which Bill wants to extend. Bill will add the aggregation label to the newly created ClusterRole for extending the default ClusterRoles.

      kind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: extend-admin-role\n  labels:\n    rbac.authorization.k8s.io/aggregate-to-admin: 'true'\nrules:\n  - verbs:\n      - create\n      - update\n      - patch\n      - delete\n    apiGroups:\n      - user.openshift.io\n    resources:\n      - groups\n

      Note: You can learn more about aggregated-cluster-roles here

      "},{"location":"how-to-guides/graph-visualization.html","title":"Graph Visualization on MTO Console","text":"

      Effortlessly associate tenants with their respective resources using the enhanced graph feature on the MTO Console. This dynamic graph illustrates the relationships between tenants and the resources they create, encompassing both MTO's proprietary resources and native Kubernetes/OpenShift elements.

      Example Graph:

        graph LR;\n      A(alpha)-->B(dev);\n      A-->C(prod);\n      B-->D(limitrange);\n      B-->E(owner-rolebinding);\n      B-->F(editor-rolebinding);\n      B-->G(viewer-rolebinding);\n      C-->H(limitrange);\n      C-->I(owner-rolebinding);\n      C-->J(editor-rolebinding);\n      C-->K(viewer-rolebinding);

      Explore with an intuitive graph that showcases the relationships between tenants and their resources. The MTO Console's graph feature simplifies the understanding of complex structures, providing you with a visual representation of your tenant's organization.

      To view the graph of your tenant, follow the steps below:

      • Navigate to Tenants page on the MTO Console using the left navigation bar.
      • Click on View of the tenant for which you want to view the graph.
      • Click on Graph tab on the tenant details page.
      "},{"location":"how-to-guides/integrating-external-keycloak.html","title":"Integrating External Keycloak","text":"

      MTO Console uses Keycloak for authentication and authorization. By default, the MTO Console uses an internal Keycloak instance that is provisioned by the Multi Tenant Operator in its own namespace. However, you can also integrate an external Keycloak instance with the MTO Console.

      This guide will help you integrate an external Keycloak instance with the MTO Console.

      "},{"location":"how-to-guides/integrating-external-keycloak.html#prerequisites","title":"Prerequisites","text":"
      • An OpenShift cluster with Multi Tenant Operator installed.
      • An external Keycloak instance.
      "},{"location":"how-to-guides/integrating-external-keycloak.html#steps","title":"Steps","text":"

      Navigate to the Keycloak console.

      • Go to your realm.
      • Click on the Clients.
      • Click on the Create button to create a new client.

      Create a new client.

      • Fill in the Client ID, Client Name and Client Protocol fields.

      • Add Valid Redirect URIs and Web Origins for the client.

      Note: The Valid Redirect URIs and Web Origins should be the URL of the MTO Console.

      • Click on the Save button.
      "},{"location":"how-to-guides/integrating-external-keycloak.html#update-integration-config","title":"Update Integration Config","text":"
      • Update the IntegrationConfig CR with the following configuration.
      integrations: \n  keycloak:\n    realm: <realm>\n    address: <keycloak-address>\n    clientName: <client-name>\n
      • Now, the MTO Console will be integrated with the external Keycloak instance.
      "},{"location":"how-to-guides/keycloak.html","title":"Setting Up User Access in Keycloak for MTO Console","text":"

      This guide walks you through the process of adding new users in Keycloak and granting them access to Multi Tenant Operator (MTO) Console.

      "},{"location":"how-to-guides/keycloak.html#accessing-keycloak-console","title":"Accessing Keycloak Console","text":"
      • Log in to the OpenShift Console.
      • Go to the 'Routes' section within the 'multi-tenant-operator' namespace.
      • Click on the Keycloak console link provided in the Routes.
      • Login using the admin credentials (default: admin/admin).
      "},{"location":"how-to-guides/keycloak.html#adding-new-users-in-keycloak","title":"Adding new Users in Keycloak","text":"
      • In the Keycloak console, switch to the mto realm.
      • Go to the Users section in the mto realm.
      • Follow the prompts to add a new user.
      • Once you add a new user, here is how the Users section would look like
      "},{"location":"how-to-guides/keycloak.html#accessing-mto-console","title":"Accessing MTO Console","text":"
      • Go back to the OpenShift Console, navigate to the Routes section, and get the URL for the MTO Console.
      • Open the MTO Console URL and log in with the newly added user credentials.

      Now, at this point, a user will be authenticated to the MTO Console. But in order to get access to view any Tenant resources, the user will need to be part of a Tenant.

      "},{"location":"how-to-guides/keycloak.html#granting-access-to-tenant-resources","title":"Granting Access to Tenant Resources","text":"
      • Open Tenant CR: In the OpenShift cluster, locate and open the Tenant Custom Resource (CR) that you wish to give access to. You will see a YAML file similar to the following example:
      apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: arsenal\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - gabriel@arsenal.com\n      groups:\n        - arsenal\n    editors:\n      users:\n        - hakimi@arsenal.com\n    viewers:\n      users:\n        - neymar@arsenal.com\n
      • Edit Tenant CR: Add the newly created user's email to the appropriate section (owners, editors, viewers) in the Tenant CR. For example, if you have created a user john@arsenal.com and wish to add them as an editor, the edited section would look like this:
      editors:\n  users:\n    - gabriel@arsenal.com\n    - benzema@arsenal.com\n
      • Save Changes: Save and apply the changes to the Tenant CR.
      "},{"location":"how-to-guides/keycloak.html#verifying-access","title":"Verifying Access","text":"

      Once the above steps are completed, you should be able to access the MTO Console now and see alpha Tenant's details along with all the other resources such as namespaces and templates that John has access to.

      "},{"location":"how-to-guides/mattermost.html","title":"Creating Mattermost Teams for your tenant","text":""},{"location":"how-to-guides/mattermost.html#requirements","title":"Requirements","text":"

      MTO-Mattermost-Integration-Operator

      Please contact stakater to install the Mattermost integration operator before following the below-mentioned steps.

      "},{"location":"how-to-guides/mattermost.html#steps-to-enable-integration","title":"Steps to enable integration","text":"

      Bill wants some tenants to also have their own Mattermost Teams. To make sure this happens correctly, Bill will first add the stakater.com/mattermost: true label to the tenants. The label will enable the mto-mattermost-integration-operator to create and manage Mattermost Teams based on Tenants.

      apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: sigma\n  labels:\n    stakater.com/mattermost: 'true'\nspec:\n  quota: medium\n  accessControl:\n    owners:\n      users:\n        - user\n    editors:\n      users:\n        - user1\n  namespaces:\n    sandboxes:\n      enabled: false\n    withTenantPrefix:\n      - dev\n      - build\n      - prod\n

      Now user can log In to Mattermost to see their Team and relevant channels associated with it.

      The name of the Team is similar to the Tenant name. Notification channels are pre-configured for every team, and can be modified.

      "},{"location":"how-to-guides/resource-sync-by-tgi.html","title":"Sync Resources Deployed by TemplateGroupInstance","text":"

      The TemplateGroupInstance CR provides two types of resource sync for the resources mentioned in Template

      For the given example, let's consider we want to apply the following template

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-secret\nresources:\n  manifests:\n\n    - kind: Secret\n      apiVersion: v1\n      metadata:\n        name: docker-pull-secret\n      data:\n        .dockercfg: eyAKICAiaHR0cHM6IC8vaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsgImF1dGgiOiAiYzNSaGEyRjBaWEk2VjI5M1YyaGhkRUZIY21WaGRGQmhjM04zYjNKayJ9Cn0K\n      type: kubernetes.io/dockercfg\n\n    - apiVersion: v1\n      kind: ServiceAccount\n      metadata:\n        name: example-automated-thing\n      secrets:\n        - name: example-automated-thing-token-zyxwv\n

      And the following TemplateGroupInstance is used to deploy these resources to namespaces having label kind: build

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: docker-secret-group-instance\nspec:\n  template: docker-secret\n  selector:\n    matchLabels:\n      kind: build\n  sync: true\n

      As we can see, in our TGI, we have a field spec.sync which is set to true. This will update the resources on two conditions:

      • The Template CR is updated
      • The TemplateGroupInstance CR is reconciled/updated

      • If, for any reason, the underlying resource gets updated or deleted, TemplateGroupInstance CR will try to revert it back to the state mentioned in the Template CR.

      Note

      Updates to ServiceAccounts are ignored by both, reconciler and informers, in an attempt to avoid conflict between the TGI controller and Kube Controller Manager. ServiceAccounts are only reverted in case of unexpected deletions when sync is true.

      "},{"location":"how-to-guides/resource-sync-by-tgi.html#ignore-resources-updates-on-resources","title":"Ignore Resources Updates on Resources","text":"

      If the resources mentioned in Template CR conflict with another controller/operator, and you want TemplateGroupInstance to not actively revert the resource updates, you can add the following label to the conflicting resource multi-tenant-operator/ignore-resource-updates: \"\".

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-secret\nresources:\n  manifests:\n\n    - kind: Secret\n      apiVersion: v1\n      metadata:\n        name: docker-pull-secret\n      data:\n        .dockercfg: eyAKICAiaHR0cHM6IC8vaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsgImF1dGgiOiAiYzNSaGEyRjBaWEk2VjI5M1YyaGhkRUZIY21WaGRGQmhjM04zYjNKayJ9Cn0K\n      type: kubernetes.io/dockercfg\n\n    - apiVersion: v1\n      kind: ServiceAccount\n      metadata:\n        name: example-automated-thing\n        labels:\n          multi-tenant-operator/ignore-resource-updates: \"\"\n      secrets:\n        - name: example-automated-thing-token-zyxwv\n

      Note

      However, this label will not stop Multi Tenant Operator from updating the resource on following conditions: - Template gets updated - TemplateGroupInstance gets updated - Resource gets deleted

      If you don't want to sync the resources in any case, you can disable sync via sync: false in TemplateGroupInstance spec.

      "},{"location":"how-to-guides/offboarding/uninstalling.html","title":"Uninstall via OperatorHub UI on OpenShift","text":"

      You can uninstall MTO by following these steps:

      • Decide on whether you want to retain tenant namespaces and ArgoCD AppProjects or not. If yes, please set spec.onDelete.cleanNamespaces to false for all those tenants whose namespaces you want to retain, and spec.onDelete.cleanAppProject to false for all those tenants whose AppProject you want to retain. For more details check out onDelete

      • In case you have enabled console, you will have to disable it first by navigating to Search -> IntegrationConfig -> tenant-operator-config and set spec.provision.console and spec.provision.showback to false.

      • Remove IntegrationConfig CR from the cluster by navigating to Search -> IntegrationConfig -> tenant-operator-config and select Delete from actions dropdown.

      • After making the required changes open OpenShift console and click on Operators, followed by Installed Operators from the side menu

      • Now click on uninstall and confirm uninstall.

      • Now the operator has been uninstalled.

      • Optional: you can also manually remove MTO's CRDs and its resources from the cluster.

      "},{"location":"how-to-guides/offboarding/uninstalling.html#notes","title":"Notes","text":"
      • For more details on how to use MTO please refer Tenant's tutorial.
      • For more details on how to extend your MTO manager ClusterRole please refer extend-default-clusterroles.
      "},{"location":"installation/openshift.html","title":"On OpenShift","text":"

      This document contains instructions on installing, uninstalling and configuring Multi Tenant Operator using OpenShift MarketPlace.

      1. OpenShift OperatorHub UI

      2. CLI/GitOps

      3. Enabling Console

      4. License configuration

      5. Uninstall

      "},{"location":"installation/openshift.html#requirements","title":"Requirements","text":"
      • An OpenShift cluster [v4.8 - v4.13]
      "},{"location":"installation/openshift.html#installing-via-operatorhub-ui","title":"Installing via OperatorHub UI","text":"
      • After opening OpenShift console click on Operators, followed by OperatorHub from the side menu
      • Now search for Multi Tenant Operator and then click on Multi Tenant Operator tile
      • Click on the install button
      • Select Updated channel. Select multi-tenant-operator to install the operator in multi-tenant-operator namespace from Installed Namespace dropdown menu. After configuring Update approval click on the install button.

      Note: Use stable channel for seamless upgrades. For Production Environment prefer Manual approval and use Automatic for Development Environment

      • Wait for the operator to be installed

      • Once successfully installed, MTO will be ready to enforce multi-tenancy in your cluster

      Note: MTO will be installed in multi-tenant-operator namespace.

      "},{"location":"installation/openshift.html#installing-via-cli-or-gitops","title":"Installing via CLI OR GitOps","text":"
      • Create namespace multi-tenant-operator
      oc create namespace multi-tenant-operator\nnamespace/multi-tenant-operator created\n
      • Create an OperatorGroup YAML for MTO and apply it in multi-tenant-operator namespace.
      oc create -f - << EOF\napiVersion: operators.coreos.com/v1\nkind: OperatorGroup\nmetadata:\n  name: tenant-operator\n  namespace: multi-tenant-operator\nEOF\noperatorgroup.operators.coreos.com/tenant-operator created\n
      • Create a subscription YAML for MTO and apply it in multi-tenant-operator namespace. To enable console set .spec.config.env[].ENABLE_CONSOLE to true. This will create a route resource, which can be used to access the Multi-Tenant-Operator console.
      oc create -f - << EOF\napiVersion: operators.coreos.com/v1alpha1\nkind: Subscription\nmetadata:\n  name: tenant-operator\n  namespace: multi-tenant-operator\nspec:\n  channel: stable\n  installPlanApproval: Automatic\n  name: tenant-operator\n  source: certified-operators\n  sourceNamespace: openshift-marketplace\n  startingCSV: tenant-operator.v0.10.0\nEOF\nsubscription.operators.coreos.com/tenant-operator created\n

      Note: To bring MTO via GitOps, add the above files in GitOps repository.

      • After creating the subscription custom resource open OpenShift console and click on Operators, followed by Installed Operators from the side menu

      • Wait for the installation to complete

      • Once the installation is complete click on Workloads, followed by Pods from the side menu and select multi-tenant-operator project

      • Once pods are up and running, MTO will be ready to enforce multi-tenancy in your cluster

      For more details and configurations check out IntegrationConfig.

      "},{"location":"installation/openshift.html#enabling-console","title":"Enabling Console","text":"

      To enable console GUI for MTO, go to Search -> IntegrationConfig -> tenant-operator-config and make sure the following fields are set to true:

      spec:\n  components:\n    console: true\n    showback: true\n

      Note: If your InstallPlan approval is set to Manual then you will have to manually approve the InstallPlan for MTO console components to be installed.

      "},{"location":"installation/openshift.html#manual-approval","title":"Manual Approval","text":"
      • Open OpenShift console and click on Operators, followed by Installed Operators from the side menu.
      • Now click on Upgrade available in front of mto-opencost or mto-prometheus.
      • Now click on Preview InstallPlan on top.
      • Now click on Approve button.
      • Now the InstallPlan will be approved, and MTO console components will be installed.
      "},{"location":"installation/openshift.html#license-configuration","title":"License Configuration","text":"

      We offer a free license with installation, and you can create max 2 Tenants with it.

      We offer a paid license as well. You need to have a configmap license created in MTO's namespace (multi-tenant-operator). To get this configmap, you can contact sales@stakater.com. It would look like this:

      apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: license\n  namespace: multi-tenant-operator\ndata:\n  payload.json: |\n    {\n        \"metaData\": {\n            \"tier\" : \"paid\",\n            \"company\": \"<company name here>\"\n        }\n    }\n  signature.base64.txt: <base64 signature here.>\n
      "},{"location":"installation/openshift.html#uninstall-via-operatorhub-ui","title":"Uninstall via OperatorHub UI","text":"

      You can uninstall MTO by following these steps:

      • Decide on whether you want to retain tenant namespaces and ArgoCD AppProjects or not. If yes, please set spec.onDelete.cleanNamespaces to false for all those tenants whose namespaces you want to retain, and spec.onDelete.cleanAppProject to false for all those tenants whose AppProject you want to retain. For more details check out onDelete

      • After making the required changes open OpenShift console and click on Operators, followed by Installed Operators from the side menu

      • Now click on uninstall and confirm uninstall.

      • Now the operator has been uninstalled.

      • Optional: you can also manually remove MTO's CRDs and its resources from the cluster.

      "},{"location":"installation/openshift.html#notes","title":"Notes","text":"
      • For more details on how to use MTO please refer Tenant tutorial.
      • For more details on how to extend your MTO manager ClusterRole please refer extend-default-clusterroles.
      "},{"location":"tutorials/distributing-resources/copying-resources.html","title":"Copying Secrets and Configmaps across Tenant Namespaces via TGI","text":"

      Bill is a cluster admin who wants to map a docker-pull-secret, present in a build namespace, in tenant namespaces where certain labels exists.

      First, Bill creates a template:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-pull-secret\nresources:\n  resourceMappings:\n    secrets:\n      - name: docker-pull-secret\n        namespace: build\n

      Once the template has been created, Bill makes a TemplateGroupInstance referring to the Template he wants to deploy with MatchLabels:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: docker-secret-group-instance\nspec:\n  template: docker-pull-secret\n  selector:\n    matchLabels:\n      kind: build\n  sync: true\n

      Afterward, Bill can see that secrets has been successfully mapped in all matching namespaces.

      kubectl get secret docker-pull-secret -n bluesky-anna-aurora-sandbox\nNAME             STATE    AGE\ndocker-pull-secret    Active   3m\n\nkubectl get secret docker-pull-secret -n alpha-dave-aurora-sandbox\nNAME             STATE    AGE\ndocker-pull-secret    Active   3m\n
      "},{"location":"tutorials/distributing-resources/copying-resources.html#mapping-resources-within-tenant-namespaces-via-ti","title":"Mapping Resources within Tenant Namespaces via TI","text":"

      Anna is a tenant owner who wants to map a docker-pull-secret, present in bluseky-build namespace, to bluesky-anna-aurora-sandbox namespace.

      First, Bill creates a template:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-pull-secret\nresources:\n  resourceMappings:\n    secrets:\n      - name: docker-pull-secret\n        namespace: bluesky-build\n

      Once the template has been created, Anna creates a TemplateInstance in bluesky-anna-aurora-sandbox namespace, referring to the Template.

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateInstance\nmetadata:\n  name: docker-secret-instance\n  namespace: bluesky-anna-aurora-sandbox\nspec:\n  template: docker-pull-secret\n  sync: true\n

      Afterward, Bill can see that secrets has been successfully mapped in all matching namespaces.

      kubectl get secret docker-pull-secret -n bluesky-anna-aurora-sandbox\nNAME             STATE    AGE\ndocker-pull-secret    Active   3m\n
      "},{"location":"tutorials/distributing-resources/distributing-manifests.html","title":"Distributing Resources in Namespaces","text":"

      Multi Tenant Operator has two Custom Resources which can cover this need using the Template CR, depending upon the conditions and preference.

      1. TemplateGroupInstance
      2. TemplateInstance
      "},{"location":"tutorials/distributing-resources/distributing-manifests.html#deploying-template-to-namespaces-via-templategroupinstances","title":"Deploying Template to Namespaces via TemplateGroupInstances","text":"

      Bill, the cluster admin, wants to deploy a docker pull secret in namespaces where certain labels exists.

      First, Bill creates a template:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: Template\nmetadata:\n  name: docker-secret\nresources:\n  manifests:\n    - kind: Secret\n      apiVersion: v1\n      metadata:\n        name: docker-pull-secret\n      data:\n        .dockercfg: eyAKICAiaHR0cHM6IC8vaW5kZXguZG9ja2VyLmlvL3YxLyI6IHsgImF1dGgiOiAiYzNSaGEyRjBaWEk2VjI5M1YyaGhkRUZIY21WaGRGQmhjM04zYjNKayJ9Cn0K\n      type: kubernetes.io/dockercfg\n

      Once the template has been created, Bill makes a TemplateGroupInstance referring to the Template he wants to deploy with template field, and the namespaces where resources are needed, using selector field:

      apiVersion: tenantoperator.stakater.com/v1alpha1\nkind: TemplateGroupInstance\nmetadata:\n  name: docker-secret-group-instance\nspec:\n  template: docker-pull-secret\n  selector:\n    matchExpressions:\n    - key: kind\n      operator: In\n      values:\n        - build\n  sync: true\n

      Afterward, Bill can see that secrets have been successfully created in all label matching namespaces.

      kubectl get secret docker-secret -n bluesky-anna-aurora-sandbox\nNAME             STATE    AGE\ndocker-secret    Active   3m\n\nkubectl get secret docker-secret -n alpha-dave-aurora-sandbox\nNAME             STATE    AGE\ndocker-secret    Active   2m\n

      TemplateGroupInstance can also target specific tenants or all tenant namespaces under a single YAML definition.

      "},{"location":"tutorials/tenant/assigning-metadata.html","title":"Assigning Metadata in Tenant Custom Resources","text":"

      In the v1beta3 version of the Tenant Custom Resource (CR), metadata assignment has been refined to offer granular control over labels and annotations across different namespaces associated with a tenant. This functionality enables precise and flexible management of metadata, catering to both general and specific needs.

      "},{"location":"tutorials/tenant/assigning-metadata.html#distributing-common-labels-and-annotations","title":"Distributing Common Labels and Annotations","text":"

      To apply common labels and annotations across all namespaces within a tenant, the namespaces.metadata.common field in the Tenant CR is utilized. This approach ensures that essential metadata is uniformly present across all namespaces, supporting consistent identification, management, and policy enforcement.

      kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    withTenantPrefix:\n      - dev\n      - build\n      - prod\n    metadata:\n      common:\n        labels:\n          app.kubernetes.io/managed-by: tenant-operator\n          app.kubernetes.io/part-of: tenant-alpha\n        annotations:\n          openshift.io/node-selector: node-role.kubernetes.io/infra=\nEOF\n

      By configuring the namespaces.metadata.common field as shown, all namespaces within the tenant will inherit the specified labels and annotations.

      "},{"location":"tutorials/tenant/assigning-metadata.html#distributing-specific-labels-and-annotations","title":"Distributing Specific Labels and Annotations","text":"

      For scenarios requiring targeted application of labels and annotations to specific namespaces, the Tenant CR's namespaces.metadata.specific field is designed. This feature enables the assignment of unique metadata to designated namespaces, accommodating specialized configurations and requirements.

      kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    withTenantPrefix:\n      - dev\n      - build\n      - prod\n    metadata:\n      specific:\n        - namespaces:\n            - bluesky-dev\n          labels:\n            app.kubernetes.io/is-sandbox: \"true\"\n          annotations:\n            openshift.io/node-selector: node-role.kubernetes.io/worker=\nEOF\n

      This configuration directs the specific labels and annotations solely to the enumerated namespaces, enabling distinct settings for particular environments.

      "},{"location":"tutorials/tenant/assigning-metadata.html#assigning-metadata-to-sandbox-namespaces","title":"Assigning Metadata to Sandbox Namespaces","text":"

      To specifically address sandbox namespaces within the tenant, the namespaces.metadata.sandbox property of the Tenant CR is employed. This section allows for the distinct management of sandbox namespaces, enhancing security and differentiation in development or testing environments.

      apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: true\n      private: true\n    metadata:\n      sandbox:\n        labels:\n          app.kubernetes.io/part-of: che.eclipse.org\n        annotations:\n          che.eclipse.org/username: \"{{ TENANT.USERNAME }}\" # templated placeholder\n

      This setup ensures that all sandbox namespaces receive the designated metadata, with support for templated values, such as {{ TENANT.USERNAME }}, allowing dynamic customization based on the tenant or user context.

      These enhancements in metadata management within the v1beta3 version of the Tenant CR provide comprehensive and flexible tools for labeling and annotating namespaces, supporting a wide range of organizational, security, and operational objectives.

      "},{"location":"tutorials/tenant/create-sandbox.html","title":"Create Sandbox Namespaces for Tenant Users","text":"

      Sandbox namespaces offer a personal development and testing space for users within a tenant. This guide covers how to enable and configure sandbox namespaces for tenant users, along with setting privacy and applying metadata specifically for these sandboxes.

      "},{"location":"tutorials/tenant/create-sandbox.html#enabling-sandbox-namespaces","title":"Enabling Sandbox Namespaces","text":"

      Bill has assigned the ownership of the tenant bluesky to Anna and Anthony. To provide them with their sandbox namespaces, he must enable the sandbox functionality in the tenant's configuration.

      To enable sandbox namespaces, Bill updates the Tenant Custom Resource (CR) with sandboxes.enabled: true:

      kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: true\nEOF\n

      This configuration automatically generates sandbox namespaces for Anna, Anthony, and even John (as an editor) with the naming convention <tenantName>-<userName>-sandbox.

      kubectl get namespaces\nNAME                             STATUS   AGE\nbluesky-anna-aurora-sandbox      Active   5d5h\nbluesky-anthony-aurora-sandbox   Active   5d5h\nbluesky-john-aurora-sandbox      Active   5d5h\n
      "},{"location":"tutorials/tenant/create-sandbox.html#creating-private-sandboxes","title":"Creating Private Sandboxes","text":"

      To address privacy concerns where users require their sandbox namespaces to be visible only to themselves, Bill can set the sandboxes.private: true in the Tenant CR:

      kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: true\n      private: true\nEOF\n

      With private: true, each sandbox namespace is accessible and visible only to its designated user, enhancing privacy and security.

      With the above configuration Anna and Anthony will now have new sandboxes created

      kubectl get namespaces\nNAME                             STATUS   AGE\nbluesky-anna-aurora-sandbox      Active   5d5h\nbluesky-anthony-aurora-sandbox   Active   5d5h\nbluesky-john-aurora-sandbox      Active   5d5h\n

      However, from the perspective of Anna, only their sandbox will be visible

      kubectl get namespaces\nNAME                             STATUS   AGE\nbluesky-anna-aurora-sandbox      Active   5d5h\n
      "},{"location":"tutorials/tenant/create-sandbox.html#applying-metadata-to-sandbox-namespaces","title":"Applying Metadata to Sandbox Namespaces","text":"

      For uniformity or to apply specific policies, Bill might need to add common metadata, such as labels or annotations, to all sandbox namespaces. This is achievable through the namespaces.metadata.sandbox configuration:

      kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: true\n      private: true\n    metadata:\n      sandbox:\n        labels:\n          app.kubernetes.io/part-of: che.eclipse.org\n        annotations:\n          che.eclipse.org/username: \"{{ TENANT.USERNAME }}\"\nEOF\n

      The templated annotation \"{{ TENANT.USERNAME }}\" dynamically inserts the username of the sandbox owner, personalizing the sandbox environment. This capability is particularly useful for integrating with other systems or applications that might utilize this metadata for configuration or access control.

      Through the examples demonstrated, Bill can efficiently manage sandbox namespaces for tenant users, ensuring they have the necessary resources for development and testing while maintaining privacy and organizational policies.

      "},{"location":"tutorials/tenant/create-tenant.html","title":"Creating a Tenant","text":"

      Bill, a cluster admin, has been tasked by the CTO of Nordmart to set up a new tenant for Anna's team. Following the request, Bill proceeds to create a new tenant named bluesky in the Kubernetes cluster.

      "},{"location":"tutorials/tenant/create-tenant.html#setting-up-the-tenant","title":"Setting Up the Tenant","text":"

      To establish the tenant, Bill crafts a Tenant Custom Resource (CR) with the necessary specifications:

      kubectl create -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: false\nEOF\n

      In this configuration, Bill specifies anna@aurora.org as the owner, giving her full administrative rights over the tenant. The editor role is assigned to john@aurora.org and the group alpha, providing them with editing capabilities within the tenant's scope.

      "},{"location":"tutorials/tenant/create-tenant.html#verifying-the-tenant-creation","title":"Verifying the Tenant Creation","text":"

      After creating the tenant, Bill checks its status to confirm it's active and operational:

      kubectl get tenants.tenantoperator.stakater.com bluesky\nNAME       STATE    AGE\nbluesky    Active   3m\n

      This output indicates that the tenant bluesky is successfully created and in an active state.

      "},{"location":"tutorials/tenant/create-tenant.html#checking-user-permissions","title":"Checking User Permissions","text":"

      To ensure the roles and permissions are correctly assigned, Anna logs into the cluster to verify her capabilities:

      Namespace Creation:

      kubectl auth can-i create namespaces\nyes\n

      Anna is confirmed to have the ability to create namespaces within the tenant's scope.

      Cluster Resources Access:

      kubectl auth can-i get namespaces\nno\n\nkubectl auth can-i get persistentvolumes\nno\n

      As expected, Anna does not have access to broader cluster resources outside the tenant's confines.

      Tenant Resource Access:

      kubectl auth can-i get tenants.tenantoperator.stakater.com\nno\n

      Access to the Tenant resource itself is also restricted, aligning with the principle of least privilege.

      "},{"location":"tutorials/tenant/create-tenant.html#adding-multiple-owners-to-a-tenant","title":"Adding Multiple Owners to a Tenant","text":"

      Later, if there's a need to grant administrative privileges to another user, such as Anthony, Bill can easily update the tenant's configuration to include multiple owners:

      kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    sandboxes:\n      enabled: false\nEOF\n

      With this update, both Anna and Anthony can administer the tenant bluesky, including the creation of namespaces:

      kubectl auth can-i create namespaces\nyes\n

      This flexible approach allows Bill to manage tenant access control efficiently, ensuring that the team's operational needs are met while maintaining security and governance standards.

      "},{"location":"tutorials/tenant/creating-namespaces.html","title":"Creating Namespaces through Tenant Custom Resource","text":"

      Bill, tasked with structuring namespaces for different environments within a tenant, utilizes the Tenant Custom Resource (CR) to streamline this process efficiently. Here's how Bill can orchestrate the creation of dev, build, and production environments for the tenant members directly through the Tenant CR.

      "},{"location":"tutorials/tenant/creating-namespaces.html#strategy-for-namespace-creation","title":"Strategy for Namespace Creation","text":"

      To facilitate the environment setup, Bill decides to categorize the namespaces based on their association with the tenant's name. He opts to use the namespaces.withTenantPrefix field for namespaces that should carry the tenant name as a prefix, enhancing clarity and organization. For namespaces that do not require a tenant name prefix, Bill employs the namespaces.withoutTenantPrefix field.

      Here's how Bill configures the Tenant CR to create these namespaces:

      kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n    editors:\n      users:\n        - john@aurora.org\n      groups:\n        - alpha\n  namespaces:\n    withTenantPrefix:\n      - dev\n      - build\n    withoutTenantPrefix:\n      - prod\nEOF\n

      This configuration ensures the creation of the desired namespaces, directly correlating them with the bluesky tenant.

      Upon applying the above configuration, Bill and the tenant members observe the creation of the following namespaces:

      kubectl get namespaces\nNAME             STATUS   AGE\nbluesky-dev      Active   5m\nbluesky-build    Active   5m\nprod             Active   5m\n

      Anna, as a tenant owner, gains the capability to further customize or create new namespaces within her tenant's scope. For example, creating a bluesky-production namespace with the necessary tenant label:

      apiVersion: v1\nkind: Namespace\nmetadata:\n  name: bluesky-production\n  labels:\n    stakater.com/tenant: bluesky\n

      \u26a0\ufe0f It's crucial for Anna to include the tenant label tenantoperator.stakater.com/tenant: bluesky to ensure the namespace is recognized as part of the bluesky tenant. Failure to do so, or if Anna is not associated with the bluesky tenant, will result in Multi Tenant Operator (MTO) denying the namespace creation.

      Following the creation, the MTO dynamically assigns roles to Anna and other tenant members according to their designated user types, ensuring proper access control and operational capabilities within these namespaces.

      "},{"location":"tutorials/tenant/creating-namespaces.html#incorporating-existing-namespaces-into-the-tenant-via-argocd","title":"Incorporating Existing Namespaces into the Tenant via ArgoCD","text":"

      For teams practicing GitOps, existing namespaces can be seamlessly integrated into the Tenant structure by appending the tenant label to the namespace's manifest within the GitOps repository. This approach allows for efficient, automated management of namespace affiliations and access controls, ensuring a cohesive tenant ecosystem.

      "},{"location":"tutorials/tenant/creating-namespaces.html#add-existing-namespaces-to-tenant-via-gitops","title":"Add Existing Namespaces to Tenant via GitOps","text":"

      Using GitOps as your preferred development workflow, you can add existing namespaces for your tenants by including the tenant label.

      To add an existing namespace to your tenant via GitOps:

      1. Migrate the namespace resource to the GitOps-monitored repository
      2. Amend the namespace manifest to include the tenant label: tenantoperator.stakater.com/tenant: .
      3. Synchronize the GitOps repository with the cluster to propagate the changes
      4. Validate that the tenant users now have appropriate access to the integrated namespace
      5. "},{"location":"tutorials/tenant/creating-namespaces.html#removing-namespaces-via-gitops","title":"Removing Namespaces via GitOps","text":"

        To disassociate or remove namespaces from the cluster through GitOps, the namespace configuration should be eliminated from the GitOps repository. Additionally, detaching the namespace from any ArgoCD-managed applications by removing the app.kubernetes.io/instance label ensures a clean removal without residual dependencies.

        Synchronizing the repository post-removal finalizes the deletion process, effectively managing the lifecycle of namespaces within a tenant-operated Kubernetes environment.

        "},{"location":"tutorials/tenant/deleting-tenant.html","title":"Deleting a Tenant While Preserving Resources","text":"

        When managing tenant lifecycles within Kubernetes, certain scenarios require the deletion of a tenant without removing associated namespaces or ArgoCD AppProjects. This ensures that resources and configurations tied to the tenant remain intact for archival or transition purposes.

        "},{"location":"tutorials/tenant/deleting-tenant.html#configuration-for-retaining-resources","title":"Configuration for Retaining Resources","text":"

        Bill decides to decommission the bluesky tenant but needs to preserve all related namespaces for continuity. To achieve this, he adjusts the Tenant Custom Resource (CR) to prevent the automatic cleanup of these resources upon tenant deletion.

        kubectl apply -f - << EOF\napiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: bluesky\nspec:\n  quota: small\n  accessControl:\n    owners:\n      users:\n        - anna@aurora.org\n        - anthony@aurora.org\n  namespaces:\n    sandboxes:\n      enabled: true\n    withTenantPrefix:\n      - dev\n      - build\n      - prod\n    onDeletePurgeNamespaces: false\nEOF\n

        With the onDeletePurgeNamespaces fields set to false, Bill ensures that the deletion of the bluesky tenant does not trigger the removal of its namespaces. This setup is crucial for cases where the retention of environment setups and deployments is necessary post-tenant deletion.

        "},{"location":"tutorials/tenant/deleting-tenant.html#default-behavior","title":"Default Behavior","text":"

        It's important to note the default behavior of the Tenant Operator regarding resource cleanup:

        Namespaces: By default, onDeletePurgeNamespaces is set to false, implying that namespaces are not automatically deleted with the tenant unless explicitly configured.

        "},{"location":"tutorials/tenant/deleting-tenant.html#deleting-the-tenant","title":"Deleting the Tenant","text":"

        Once the Tenant CR is configured as desired, Bill can proceed to delete the bluesky tenant:

        kubectl delete tenant bluesky\n

        This command removes the tenant resource from the cluster while leaving the specified namespaces untouched, adhering to the configured onDeletePurgeNamespaces policies. This approach provides flexibility in managing the lifecycle of tenant resources, catering to various operational strategies and compliance requirements.

        "},{"location":"tutorials/tenant/tenant-hibernation.html","title":"Hibernating a Tenant","text":"

        Implementing hibernation for tenants' namespaces efficiently manages cluster resources by temporarily reducing workload activities during off-peak hours. This guide demonstrates how to configure hibernation schedules for tenant namespaces, leveraging Tenant and ResourceSupervisor for precise control.

        "},{"location":"tutorials/tenant/tenant-hibernation.html#configuring-hibernation-for-tenant-namespaces","title":"Configuring Hibernation for Tenant Namespaces","text":"

        You can manage workloads in your cluster with MTO by implementing a hibernation schedule for your tenants. Hibernation downsizes the running Deployments and StatefulSets in a tenant\u2019s namespace according to a defined cron schedule. You can set a hibernation schedule for your tenants by adding the \u2018spec.hibernation\u2019 field to the tenant's respective Custom Resource.

        hibernation:\n  sleepSchedule: 23 * * * *\n  wakeSchedule: 26 * * * *\n

        spec.hibernation.sleepSchedule accepts a cron expression indicating the time to put the workloads in your tenant\u2019s namespaces to sleep.

        spec.hibernation.wakeSchedule accepts a cron expression indicating the time to wake the workloads in your tenant\u2019s namespaces up.

        Note

        Both sleep and wake schedules must be specified for your Hibernation schedule to be valid.

        Additionally, adding the hibernation.stakater.com/exclude: 'true' annotation to a namespace excludes it from hibernating.

        Note

        This is only true for hibernation applied via the Tenant Custom Resource, and does not apply for hibernation done by manually creating a ResourceSupervisor (details about that below).

        Note

        This will not wake up an already sleeping namespace before the wake schedule.

        "},{"location":"tutorials/tenant/tenant-hibernation.html#resource-supervisor","title":"Resource Supervisor","text":"

        Adding a Hibernation Schedule to a Tenant creates an accompanying ResourceSupervisor Custom Resource. The Resource Supervisor stores the Hibernation schedules and manages the current and previous states of all the applications, whether they are sleeping or awake.

        When the sleep timer is activated, the Resource Supervisor controller stores the details of your applications (including the number of replicas, configurations, etc.) in the applications' namespaces and then puts your applications to sleep. When the wake timer is activated, the controller wakes up the applications using their stored details.

        Enabling ArgoCD support for Tenants will also hibernate applications in the tenants' appProjects.

        apiVersion: tenantoperator.stakater.com/v1beta1\nkind: ResourceSupervisor\nmetadata:\n  name: sigma\nspec:\n  argocd:\n    appProjects:\n      - sigma\n    namespace: openshift-gitops\n  hibernation:\n    sleepSchedule: 42 * * * *\n    wakeSchedule: 45 * * * *\n  namespaces:\n    - tenant-ns1\n    - tenant-ns2\n

        Currently, Hibernation is available only for StatefulSets and Deployments.

        "},{"location":"tutorials/tenant/tenant-hibernation.html#manual-creation-of-resourcesupervisor","title":"Manual creation of ResourceSupervisor","text":"

        Hibernation can also be applied by creating a ResourceSupervisor resource manually. The ResourceSupervisor definition will contain the hibernation cron schedule, the names of the namespaces to be hibernated, and the names of the ArgoCD AppProjects whose ArgoCD Applications have to be hibernated (as per the given schedule).

        This method can be used to hibernate:

        • Some specific namespaces and AppProjects in a tenant
        • A set of namespaces and AppProjects belonging to different tenants
        • Namespaces and AppProjects belonging to a tenant that the cluster admin is not a member of
        • Non-tenant namespaces and ArgoCD AppProjects

        As an example, the following ResourceSupervisor could be created manually, to apply hibernation explicitly to the 'ns1' and 'ns2' namespaces, and to the 'sample-app-project' AppProject.

        apiVersion: tenantoperator.stakater.com/v1beta1\nkind: ResourceSupervisor\nmetadata:\n  name: hibernator\nspec:\n  argocd:\n    appProjects:\n      - sample-app-project\n    namespace: openshift-gitops\n  hibernation:\n    sleepSchedule: 42 * * * *\n    wakeSchedule: 45 * * * *\n  namespaces:\n    - ns1\n    - ns2\n
        "},{"location":"tutorials/tenant/tenant-hibernation.html#freeing-up-unused-resources-with-hibernation","title":"Freeing up unused resources with hibernation","text":""},{"location":"tutorials/tenant/tenant-hibernation.html#hibernating-a-tenant_1","title":"Hibernating a tenant","text":"

        Bill is a cluster administrator who wants to free up unused cluster resources at nighttime, in an effort to reduce costs (when the cluster isn't being used).

        First, Bill creates a tenant with the hibernation schedules mentioned in the spec, or adds the hibernation field to an existing tenant:

        apiVersion: tenantoperator.stakater.com/v1beta3\nkind: Tenant\nmetadata:\n  name: sigma\nspec:\n  hibernation:\n    sleepSchedule: \"0 20 * * 1-5\"  # Sleep at 8 PM on weekdays\n    wakeSchedule: \"0 8 * * 1-5\"    # Wake at 8 AM on weekdays\n  owners:\n    users:\n      - user@example.com\n  quota: medium\n  namespaces:\n    withoutTenantPrefix:\n      - dev\n      - stage\n      - build\n

        The schedules above will put all the Deployments and StatefulSets within the tenant's namespaces to sleep, by reducing their pod count to 0 at 8 PM every weekday. At 8 AM on weekdays, the namespaces will then wake up by restoring their applications' previous pod counts.

        Bill can verify this behaviour by checking the newly created ResourceSupervisor resource at run time:

        oc get ResourceSupervisor -A\nNAME           AGE\nsigma          5m\n

        The ResourceSupervisor will look like this at 'running' time (as per the schedule):

        apiVersion: tenantoperator.stakater.com/v1beta1\nkind: ResourceSupervisor\nmetadata:\n  name: example\nspec:\n  argocd:\n    appProjects: []\n    namespace: ''\n  hibernation:\n    sleepSchedule: 0 20 * * 1-5\n    wakeSchedule: 0 8 * * 1-5\n  namespaces:\n    - build\n    - stage\n    - dev\nstatus:\n  currentStatus: running\n  nextReconcileTime: '2022-10-12T20:00:00Z'\n

        The ResourceSupervisor will look like this at 'sleeping' time (as per the schedule):

        apiVersion: tenantoperator.stakater.com/v1beta1\nkind: ResourceSupervisor\nmetadata:\n  name: example\nspec:\n  argocd:\n    appProjects: []\n    namespace: ''\n  hibernation:\n    sleepSchedule: 0 20 * * 1-5\n    wakeSchedule: 0 8 * * 1-5\n  namespaces:\n    - build\n    - stage\n    - dev\nstatus:\n  currentStatus: sleeping\n  nextReconcileTime: '2022-10-13T08:00:00Z'\n  sleepingApplications:\n    - Namespace: build\n      kind: Deployment\n      name: example\n      replicas: 3\n    - Namespace: stage\n      kind: Deployment\n      name: example\n      replicas: 3\n

        Bill wants to prevent the build namespace from going to sleep, so he can add the hibernation.stakater.com/exclude: 'true' annotation to it. The ResourceSupervisor will now look like this after reconciling:

        apiVersion: tenantoperator.stakater.com/v1beta1\nkind: ResourceSupervisor\nmetadata:\n  name: example\nspec:\n  argocd:\n    appProjects: []\n    namespace: ''\n  hibernation:\n    sleepSchedule: 0 20 * * 1-5\n    wakeSchedule: 0 8 * * 1-5\n  namespaces:\n    - stage\n    - dev\nstatus:\n  currentStatus: sleeping\n  nextReconcileTime: '2022-10-13T08:00:00Z'\n  sleepingApplications:\n    - Namespace: stage\n      kind: Deployment\n      name: example\n      replicas: 3\n
        "},{"location":"tutorials/tenant/tenant-hibernation.html#hibernating-namespaces-andor-argocd-applications-with-resourcesupervisor","title":"Hibernating namespaces and/or ArgoCD Applications with ResourceSupervisor","text":"

        Bill, the cluster administrator, wants to hibernate a collection of namespaces and AppProjects belonging to multiple different tenants. He can do so by creating a ResourceSupervisor manually, specifying the hibernation schedule in its spec, the namespaces and ArgoCD Applications that need to be hibernated as per the mentioned schedule. Bill can also use the same method to hibernate some namespaces and ArgoCD Applications that do not belong to any tenant on his cluster.

        The example given below will hibernate the ArgoCD Applications in the 'test-app-project' AppProject; and it will also hibernate the 'ns2' and 'ns4' namespaces.

        apiVersion: tenantoperator.stakater.com/v1beta1\nkind: ResourceSupervisor\nmetadata:\n  name: test-resource-supervisor\nspec:\n  argocd:\n    appProjects:\n      - test-app-project\n    namespace: argocd-ns\n  hibernation:\n    sleepSchedule: 0 20 * * 1-5\n    wakeSchedule: 0 8 * * 1-5\n  namespaces:\n    - ns2\n    - ns4\nstatus:\n  currentStatus: sleeping\n  nextReconcileTime: '2022-10-13T08:00:00Z'\n  sleepingApplications:\n    - Namespace: ns2\n      kind: Deployment\n      name: test-deployment\n      replicas: 3\n
        "}]} \ No newline at end of file diff --git a/0.12/sitemap.xml b/0.12/sitemap.xml index f97a81cd8..ff37398b3 100644 --- a/0.12/sitemap.xml +++ b/0.12/sitemap.xml @@ -2,212 +2,212 @@ https://docs.stakater.com/mto/0.12/index.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/changelog.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/eula.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/troubleshooting.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/crds-api-reference/extensions.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/crds-api-reference/integration-config.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/crds-api-reference/quota.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/crds-api-reference/template-group-instance.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/crds-api-reference/template-instance.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/crds-api-reference/template.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/crds-api-reference/tenant.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/explanation/console.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/explanation/logs-metrics.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/explanation/multi-tenancy-vault.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/explanation/template.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/explanation/templated-metadata-values.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/configuring-multitenant-network-isolation.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/copying-resources.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/custom-metrics.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/custom-roles.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/deploying-private-helm-charts.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/deploying-templates.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/distributing-secrets-using-sealed-secret-template.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/enabling-multi-tenancy-argocd.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/enabling-multi-tenancy-vault.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/enabling-openshift-dev-workspace.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/extend-default-roles.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/graph-visualization.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/integrating-external-keycloak.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/keycloak.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/mattermost.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/resource-sync-by-tgi.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/how-to-guides/offboarding/uninstalling.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/installation/openshift.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/tutorials/distributing-resources/copying-resources.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/tutorials/distributing-resources/distributing-manifests.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/tutorials/tenant/assigning-metadata.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/tutorials/tenant/create-sandbox.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/tutorials/tenant/create-tenant.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/tutorials/tenant/creating-namespaces.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/tutorials/tenant/deleting-tenant.html - 2024-07-08 + 2024-07-24 daily https://docs.stakater.com/mto/0.12/tutorials/tenant/tenant-hibernation.html - 2024-07-08 + 2024-07-24 daily \ No newline at end of file diff --git a/0.12/sitemap.xml.gz b/0.12/sitemap.xml.gz index 35a38f731..8272d89ec 100644 Binary files a/0.12/sitemap.xml.gz and b/0.12/sitemap.xml.gz differ diff --git a/0.12/troubleshooting.html b/0.12/troubleshooting.html index 2c01fac08..11d76407e 100644 --- a/0.12/troubleshooting.html +++ b/0.12/troubleshooting.html @@ -16,7 +16,7 @@ - + @@ -24,7 +24,7 @@ - + @@ -902,7 +902,7 @@
        - +
        GitHub diff --git a/0.12/tutorials/distributing-resources/copying-resources.html b/0.12/tutorials/distributing-resources/copying-resources.html index b31e3d755..ed552e2ec 100644 --- a/0.12/tutorials/distributing-resources/copying-resources.html +++ b/0.12/tutorials/distributing-resources/copying-resources.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
        - +
        GitHub diff --git a/0.12/tutorials/distributing-resources/distributing-manifests.html b/0.12/tutorials/distributing-resources/distributing-manifests.html index 493f78d43..8a8a63b1d 100644 --- a/0.12/tutorials/distributing-resources/distributing-manifests.html +++ b/0.12/tutorials/distributing-resources/distributing-manifests.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
        - +
        GitHub diff --git a/0.12/tutorials/tenant/assigning-metadata.html b/0.12/tutorials/tenant/assigning-metadata.html index 2f3332070..f37ae1fd6 100644 --- a/0.12/tutorials/tenant/assigning-metadata.html +++ b/0.12/tutorials/tenant/assigning-metadata.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
        - +
        GitHub diff --git a/0.12/tutorials/tenant/create-sandbox.html b/0.12/tutorials/tenant/create-sandbox.html index 2ffd20758..54e7f1313 100644 --- a/0.12/tutorials/tenant/create-sandbox.html +++ b/0.12/tutorials/tenant/create-sandbox.html @@ -18,7 +18,7 @@ - + @@ -26,7 +26,7 @@ - + @@ -904,7 +904,7 @@
        - +