diff --git a/asciidoctor.css b/asciidoctor.css new file mode 100644 index 00000000..06d52e68 --- /dev/null +++ b/asciidoctor.css @@ -0,0 +1,399 @@ +/* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */ +/* Remove the comments around the @import statement below when using this as a custom stylesheet */ +/*@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic|Noto+Serif:400,400italic,700,700italic|Droid+Sans+Mono:400";*/ +article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block} +audio,canvas,video{display:inline-block} +audio:not([controls]){display:none;height:0} +[hidden],template{display:none} +script{display:none!important} +html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%} +body{margin:0} +a{background:transparent} +a:focus{outline:thin dotted} +a:active,a:hover{outline:0} +h1{font-size:2em;margin:.67em 0} +abbr[title]{border-bottom:1px dotted} +b,strong{font-weight:bold} +dfn{font-style:italic} +hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0} +mark{background:#ff0;color:#000} +code,kbd,pre,samp{font-family:monospace;font-size:1em} +pre{white-space:pre-wrap} +q{quotes:"\201C" "\201D" "\2018" "\2019"} +small{font-size:80%} +sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} +sup{top:-.5em} +sub{bottom:-.25em} +img{border:0} +svg:not(:root){overflow:hidden} +figure{margin:0} +fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em} +legend{border:0;padding:0} +button,input,select,textarea{font-family:inherit;font-size:100%;margin:0} +button,input{line-height:normal} +button,select{text-transform:none} +button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer} +button[disabled],html input[disabled]{cursor:default} +input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0} +input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box} +input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none} +button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0} +textarea{overflow:auto;vertical-align:top} +table{border-collapse:collapse;border-spacing:0} +*,*:before,*:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box} +html,body{font-size:100%} +body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto} +a:hover{cursor:pointer} +img,object,embed{max-width:100%;height:auto} +object,embed{height:100%} +img{-ms-interpolation-mode:bicubic} +#map_canvas img,#map_canvas embed,#map_canvas object,.map_canvas img,.map_canvas embed,.map_canvas object{max-width:none!important} +.left{float:left!important} +.right{float:right!important} +.text-left{text-align:left!important} +.text-right{text-align:right!important} +.text-center{text-align:center!important} +.text-justify{text-align:justify!important} +.hide{display:none} +.antialiased,body{-webkit-font-smoothing:antialiased} +img{display:inline-block;vertical-align:middle} +textarea{height:auto;min-height:50px} +select{width:100%} +p.lead,.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{font-size:1.21875em;line-height:1.6} +.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em} +div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr} +a{color:#2156a5;text-decoration:underline;line-height:inherit} +a:hover,a:focus{color:#1d4b8f} +a img{border:none} +p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility} +p aside{font-size:.875em;line-height:1.35;font-style:italic} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em} +h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0} +h1{font-size:2.125em} +h2{font-size:1.6875em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em} +h4,h5{font-size:1.125em} +h6{font-size:1em} +hr{border:solid #ddddd8;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0} +em,i{font-style:italic;line-height:inherit} +strong,b{font-weight:bold;line-height:inherit} +small{font-size:60%;line-height:inherit} +code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)} +ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit} +ul,ol,ul.no-bullet,ol.no-bullet{margin-left:1.5em} +ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em} +ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit} +ul.square{list-style-type:square} +ul.circle{list-style-type:circle} +ul.disc{list-style-type:disc} +ul.no-bullet{list-style:none} +ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0} +dl dt{margin-bottom:.3125em;font-weight:bold} +dl dd{margin-bottom:1.25em} +abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help} +abbr{text-transform:none} +blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd} +blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)} +blockquote cite:before{content:"\2014 \0020"} +blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)} +blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)} +@media only screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2} +h1{font-size:2.75em} +h2{font-size:2.3125em} +h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em} +h4{font-size:1.4375em}}table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede} +table thead,table tfoot{background:#f7f8f7;font-weight:bold} +table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left} +table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)} +table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7} +table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6} +h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em} +h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400} +.clearfix:before,.clearfix:after,.float-group:before,.float-group:after{content:" ";display:table} +.clearfix:after,.float-group:after{clear:both} +*:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed} +pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed} +.keyseq{color:rgba(51,51,51,.8)} +kbd{display:inline-block;color:rgba(0,0,0,.8);font-size:.75em;line-height:1.4;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:-.15em .15em 0 .15em;padding:.2em .6em .2em .5em;vertical-align:middle;white-space:nowrap} +.keyseq kbd:first-child{margin-left:0} +.keyseq kbd:last-child{margin-right:0} +.menuseq,.menu{color:rgba(0,0,0,.8)} +b.button:before,b.button:after{position:relative;top:-1px;font-weight:400} +b.button:before{content:"[";padding:0 3px 0 2px} +b.button:after{content:"]";padding:0 2px 0 3px} +p a>code:hover{color:rgba(0,0,0,.9)} +#header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em} +#header:before,#header:after,#content:before,#content:after,#footnotes:before,#footnotes:after,#footer:before,#footer:after{content:" ";display:table} +#header:after,#content:after,#footnotes:after,#footer:after{clear:both} +#content{margin-top:1.25em} +#content:before{content:none} +#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0} +#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #ddddd8} +#header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px} +#header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap} +#header .details span:first-child{margin-left:-.125em} +#header .details span.email a{color:rgba(0,0,0,.85)} +#header .details br{display:none} +#header .details br+span:before{content:"\00a0\2013\00a0"} +#header .details br+span.author:before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)} +#header .details br+span#revremark:before{content:"\00a0|\00a0"} +#header #revnumber{text-transform:capitalize} +#header #revnumber:after{content:"\00a0"} +#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem} +#toc{border-bottom:1px solid #efefed;padding-bottom:.5em} +#toc>ul{margin-left:.125em} +#toc ul.sectlevel0>li>a{font-style:italic} +#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0} +#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none} +#toc a{text-decoration:none} +#toc a:active{text-decoration:underline} +#toctitle{color:#7a2518;font-size:1.2em} +@media only screen and (min-width:768px){#toctitle{font-size:1.375em} +body.toc2{padding-left:15em;padding-right:0} +#toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #efefed;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto} +#toc.toc2 #toctitle{margin-top:0;font-size:1.2em} +#toc.toc2>ul{font-size:.9em;margin-bottom:0} +#toc.toc2 ul ul{margin-left:0;padding-left:1em} +#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em} +body.toc2.toc-right{padding-left:0;padding-right:15em} +body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #efefed;left:auto;right:0}}@media only screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0} +#toc.toc2{width:20em} +#toc.toc2 #toctitle{font-size:1.375em} +#toc.toc2>ul{font-size:.95em} +#toc.toc2 ul ul{padding-left:1.25em} +body.toc2.toc-right{padding-left:0;padding-right:20em}}#content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +#content #toc>:first-child{margin-top:0} +#content #toc>:last-child{margin-bottom:0} +#footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em} +#footer-text{color:rgba(255,255,255,.8);line-height:1.44} +.sect1{padding-bottom:.625em} +@media only screen and (min-width:768px){.sect1{padding-bottom:1.25em}}.sect1+.sect1{border-top:1px solid #efefed} +#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400} +#content h1>a.anchor:before,h2>a.anchor:before,h3>a.anchor:before,#toctitle>a.anchor:before,.sidebarblock>.content>.title>a.anchor:before,h4>a.anchor:before,h5>a.anchor:before,h6>a.anchor:before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em} +#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible} +#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none} +#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221} +.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em} +.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic} +table.tableblock>caption.title{white-space:nowrap;overflow:visible;max-width:0} +.paragraph.lead>p,#preamble>.sectionbody>.paragraph:first-of-type p{color:rgba(0,0,0,.85)} +table.tableblock #preamble>.sectionbody>.paragraph:first-of-type p{font-size:inherit} +.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%} +.admonitionblock>table td.icon{text-align:center;width:80px} +.admonitionblock>table td.icon img{max-width:none} +.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase} +.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #ddddd8;color:rgba(0,0,0,.6)} +.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0} +.exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px} +.exampleblock>.content>:first-child{margin-top:0} +.exampleblock>.content>:last-child{margin-bottom:0} +.sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px} +.sidebarblock>:first-child{margin-top:0} +.sidebarblock>:last-child{margin-bottom:0} +.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center} +.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0} +.literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8} +.sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1} +.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;padding:1em;font-size:.8125em} +.literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto;white-space:pre;word-wrap:normal} +@media only screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}}@media only screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}}.literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)} +.listingblock pre.highlightjs{padding:0} +.listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px} +.listingblock pre.prettyprint{border-width:0} +.listingblock>.content{position:relative} +.listingblock code[data-lang]:before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999} +.listingblock:hover code[data-lang]:before{display:block} +.listingblock.terminal pre .command:before{content:attr(data-prompt);padding-right:.5em;color:#999} +.listingblock.terminal pre .command:not([data-prompt]):before{content:"$"} +table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none} +table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0} +table.pyhltable td.code{padding-left:.75em;padding-right:0} +pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8} +pre.pygments .lineno{display:inline-block;margin-right:.25em} +table.pyhltable .linenodiv{background:none!important;padding-right:0!important} +.quoteblock{margin:0 1em 1.25em 1.5em;display:table} +.quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em} +.quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify} +.quoteblock blockquote{margin:0;padding:0;border:0} +.quoteblock blockquote:before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)} +.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0} +.quoteblock .attribution{margin-top:.5em;margin-right:.5ex;text-align:right} +.quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)} +.quoteblock .quoteblock blockquote{padding:0 0 0 .75em} +.quoteblock .quoteblock blockquote:before{display:none} +.verseblock{margin:0 1em 1.25em 1em} +.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility} +.verseblock pre strong{font-weight:400} +.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex} +.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic} +.quoteblock .attribution br,.verseblock .attribution br{display:none} +.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.05em;color:rgba(0,0,0,.6)} +.quoteblock.abstract{margin:0 0 1.25em 0;display:block} +.quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{text-align:left;word-spacing:0} +.quoteblock.abstract blockquote:before,.quoteblock.abstract blockquote p:first-of-type:before{display:none} +table.tableblock{max-width:100%;border-collapse:separate} +table.tableblock td>.paragraph:last-child p>p:last-child,table.tableblock th>p:last-child,table.tableblock td>p:last-child{margin-bottom:0} +table.spread{width:100%} +table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede} +table.grid-all th.tableblock,table.grid-all td.tableblock{border-width:0 1px 1px 0} +table.grid-all tfoot>tr>th.tableblock,table.grid-all tfoot>tr>td.tableblock{border-width:1px 1px 0 0} +table.grid-cols th.tableblock,table.grid-cols td.tableblock{border-width:0 1px 0 0} +table.grid-all *>tr>.tableblock:last-child,table.grid-cols *>tr>.tableblock:last-child{border-right-width:0} +table.grid-rows th.tableblock,table.grid-rows td.tableblock{border-width:0 0 1px 0} +table.grid-all tbody>tr:last-child>th.tableblock,table.grid-all tbody>tr:last-child>td.tableblock,table.grid-all thead:last-child>tr>th.tableblock,table.grid-rows tbody>tr:last-child>th.tableblock,table.grid-rows tbody>tr:last-child>td.tableblock,table.grid-rows thead:last-child>tr>th.tableblock{border-bottom-width:0} +table.grid-rows tfoot>tr>th.tableblock,table.grid-rows tfoot>tr>td.tableblock{border-width:1px 0 0 0} +table.frame-all{border-width:1px} +table.frame-sides{border-width:0 1px} +table.frame-topbot{border-width:1px 0} +th.halign-left,td.halign-left{text-align:left} +th.halign-right,td.halign-right{text-align:right} +th.halign-center,td.halign-center{text-align:center} +th.valign-top,td.valign-top{vertical-align:top} +th.valign-bottom,td.valign-bottom{vertical-align:bottom} +th.valign-middle,td.valign-middle{vertical-align:middle} +table thead th,table tfoot th{font-weight:bold} +tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7} +tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold} +p.tableblock>code:only-child{background:none;padding:0} +p.tableblock{font-size:1em} +td>div.verse{white-space:pre} +ol{margin-left:1.75em} +ul li ol{margin-left:1.5em} +dl dd{margin-left:1.125em} +dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0} +ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em} +ul.unstyled,ol.unnumbered,ul.checklist,ul.none{list-style-type:none} +ul.unstyled,ol.unnumbered,ul.checklist{margin-left:.625em} +ul.checklist li>p:first-child>.fa-check-square-o:first-child,ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em} +ul.checklist li>p:first-child>input[type="checkbox"]:first-child{position:relative;top:1px} +ul.inline{margin:0 auto .625em auto;margin-left:-1.375em;margin-right:0;padding:0;list-style:none;overflow:hidden} +ul.inline>li{list-style:none;float:left;margin-left:1.375em;display:block} +ul.inline>li>*{display:block} +.unstyled dl dt{font-weight:400;font-style:normal} +ol.arabic{list-style-type:decimal} +ol.decimal{list-style-type:decimal-leading-zero} +ol.loweralpha{list-style-type:lower-alpha} +ol.upperalpha{list-style-type:upper-alpha} +ol.lowerroman{list-style-type:lower-roman} +ol.upperroman{list-style-type:upper-roman} +ol.lowergreek{list-style-type:lower-greek} +.hdlist>table,.colist>table{border:0;background:none} +.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none} +td.hdlist1{padding-right:.75em;font-weight:bold} +td.hdlist1,td.hdlist2{vertical-align:top} +.literalblock+.colist,.listingblock+.colist{margin-top:-.5em} +.colist>table tr>td:first-of-type{padding:0 .75em;line-height:1} +.colist>table tr>td:last-of-type{padding:.25em 0} +.thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd} +.imageblock.left,.imageblock[style*="float: left"]{margin:.25em .625em 1.25em 0} +.imageblock.right,.imageblock[style*="float: right"]{margin:.25em 0 1.25em .625em} +.imageblock>.title{margin-bottom:0} +.imageblock.thumb,.imageblock.th{border-width:6px} +.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em} +.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0} +.image.left{margin-right:.625em} +.image.right{margin-left:.625em} +a.image{text-decoration:none} +span.footnote,span.footnoteref{vertical-align:super;font-size:.875em} +span.footnote a,span.footnoteref a{text-decoration:none} +span.footnote a:active,span.footnoteref a:active{text-decoration:underline} +#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em} +#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em 0;border-width:1px 0 0 0} +#footnotes .footnote{padding:0 .375em;line-height:1.3;font-size:.875em;margin-left:1.2em;text-indent:-1.2em;margin-bottom:.2em} +#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none} +#footnotes .footnote:last-of-type{margin-bottom:0} +#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0} +.gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0} +.gist .file-data>table td.line-data{width:99%} +div.unbreakable{page-break-inside:avoid} +.big{font-size:larger} +.small{font-size:smaller} +.underline{text-decoration:underline} +.overline{text-decoration:overline} +.line-through{text-decoration:line-through} +.aqua{color:#00bfbf} +.aqua-background{background-color:#00fafa} +.black{color:#000} +.black-background{background-color:#000} +.blue{color:#0000bf} +.blue-background{background-color:#0000fa} +.fuchsia{color:#bf00bf} +.fuchsia-background{background-color:#fa00fa} +.gray{color:#606060} +.gray-background{background-color:#7d7d7d} +.green{color:#006000} +.green-background{background-color:#007d00} +.lime{color:#00bf00} +.lime-background{background-color:#00fa00} +.maroon{color:#600000} +.maroon-background{background-color:#7d0000} +.navy{color:#000060} +.navy-background{background-color:#00007d} +.olive{color:#606000} +.olive-background{background-color:#7d7d00} +.purple{color:#600060} +.purple-background{background-color:#7d007d} +.red{color:#bf0000} +.red-background{background-color:#fa0000} +.silver{color:#909090} +.silver-background{background-color:#bcbcbc} +.teal{color:#006060} +.teal-background{background-color:#007d7d} +.white{color:#bfbfbf} +.white-background{background-color:#fafafa} +.yellow{color:#bfbf00} +.yellow-background{background-color:#fafa00} +span.icon>.fa{cursor:default} +.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default} +.admonitionblock td.icon .icon-note:before{content:"\f05a";color:#19407c} +.admonitionblock td.icon .icon-tip:before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111} +.admonitionblock td.icon .icon-warning:before{content:"\f071";color:#bf6900} +.admonitionblock td.icon .icon-caution:before{content:"\f06d";color:#bf3400} +.admonitionblock td.icon .icon-important:before{content:"\f06a";color:#bf0000} +.conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} +.conum[data-value] *{color:#fff!important} +.conum[data-value]+b{display:none} +.conum[data-value]:after{content:attr(data-value)} +pre .conum[data-value]{position:relative;top:-.125em} +b.conum *{color:inherit!important} +.conum:not([data-value]):empty{display:none} +h1,h2{letter-spacing:-.01em} +dt,th.tableblock,td.content{text-rendering:optimizeLegibility} +p,td.content{letter-spacing:-.01em} +p strong,td.content strong{letter-spacing:-.005em} +p,blockquote,dt,td.content{font-size:1.0625rem} +p{margin-bottom:1.25rem} +.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em} +.exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc} +.print-only{display:none!important} +@media print{@page{margin:1.25cm .75cm} +*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important} +a{color:inherit!important;text-decoration:underline!important} +a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important} +a[href^="http:"]:not(.bare):after,a[href^="https:"]:not(.bare):after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em} +abbr[title]:after{content:" (" attr(title) ")"} +pre,blockquote,tr,img{page-break-inside:avoid} +thead{display:table-header-group} +img{max-width:100%!important} +p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3} +h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid} +#toc,.sidebarblock,.exampleblock>.content{background:none!important} +#toc{border-bottom:1px solid #ddddd8!important;padding-bottom:0!important} +.sect1{padding-bottom:0!important} +.sect1+.sect1{border:0!important} +#header>h1:first-child{margin-top:1.25rem} +body.book #header{text-align:center} +body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em 0} +body.book #header .details{border:0!important;display:block;padding:0!important} +body.book #header .details span:first-child{margin-left:0!important} +body.book #header .details br{display:block} +body.book #header .details br+span:before{content:none!important} +body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important} +body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always} +.listingblock code[data-lang]:before{display:block} +#footer{background:none!important;padding:0 .9375em} +#footer-text{color:rgba(0,0,0,.6)!important;font-size:.9em} +.hide-on-print{display:none!important} +.print-only{display:block!important} +.hide-for-print{display:none!important} +.show-for-print{display:inherit!important}} \ No newline at end of file diff --git a/ghpages.sh b/ghpages.sh new file mode 100644 index 00000000..91905757 --- /dev/null +++ b/ghpages.sh @@ -0,0 +1,46 @@ +#!/bin/bash -x + +git remote set-url --push origin `git config remote.origin.url | sed -e 's/^git:/https:/'` + +if ! (git remote set-branches --add origin gh-pages && git fetch -q); then + echo "No gh-pages, so not syncing" + exit 0 +fi + +if ! [ -d docs/target/generated-docs ]; then + echo "No gh-pages sources in docs/target/generated-docs, so not syncing" + exit 0 +fi + +# Stash any outstanding changes +################################################################### +git diff-index --quiet HEAD +dirty=$? +if [ "$dirty" != "0" ]; then git stash; fi + +# Switch to gh-pages branch to sync it with master +################################################################### +git checkout gh-pages + +for f in docs/target/generated-docs/*; do + file=${f#docs/target/generated-docs/*} + if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then + # Not ignored... + cp -rf $f . + git add -A $file + fi +done + +git commit -a -m "Sync docs from master to gh-pages" + +# Uncomment the following push if you want to auto push to +# the gh-pages branch whenever you commit to master locally. +# This is a little extreme. Use with care! +################################################################### +git push origin gh-pages + +# Finally, switch back to the master branch and exit block +git checkout master +if [ "$dirty" != "0" ]; then git stash pop; fi + +exit 0 diff --git a/spring-cloud-config.html b/spring-cloud-config.html index 074f76a5..28cc875a 100644 --- a/spring-cloud-config.html +++ b/spring-cloud-config.html @@ -418,7 +418,26 @@ body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-b
  • Quick Start +
  • +
  • Spring Cloud Config Server + +
  • +
  • Spring Cloud Config Client +
  • @@ -481,9 +500,84 @@ list of properties), and "label" is an optional git label (defaults to

    Client Side Usage

    To use these features in an application, just build it as a Spring -Boot application that depends on spring-cloud-config-client -(e.g. see the test cases for the config-client, or the sample app). -When it runs it will pick up the external configuration from the +Boot application that depends on spring-cloud-config-client (e.g. see +the test cases for the config-client, or the sample app). The most +convenient way to add the dependency is via a Spring Boot starter +org.springframework.cloud:spring-cloud-starter. There is also a +parent pom and BOM (spring-cloud-starters) for Maven users and a +Spring IO version management properties file for Gradle and Spring CLI +users. Example Maven configuration:

    +
    +
    +
    pom.xml
    +
    +
    <parent>
    +    <groupId>org.springframework.boot</groupId>
    +    <artifactId>spring-boot-starter-parent</artifactId>
    +    <version>1.1.7.RELEASE</version>
    +    <relativePath /> <!-- lookup parent from repository -->
    +</parent>
    +
    +<dependencyManagement>
    +    <dependencies>
    +        <dependency>
    +            <groupId>org.springframework.cloud</groupId>
    +            <artifactId>spring-cloud-starters</artifactId>
    +            <version>1.0.0.BUILD-SNAPSHOT</version>
    +            <type>pom</type>
    +            <scope>import</scope>
    +        </dependency>
    +    </dependencies>
    +</dependencyManagement>
    +
    +<dependencies>
    +    <dependency>
    +        <groupId>org.springframework.cloud</groupId>
    +        <artifactId>spring-cloud-starter</artifactId>
    +    </dependency>
    +    <dependency>
    +        <groupId>org.springframework.boot</groupId>
    +        <artifactId>spring-boot-starter-test</artifactId>
    +        <scope>test</scope>
    +    </dependency>
    +</dependencies>
    +
    +<build>
    +    <plugins>
    +        <plugin>
    +            <groupId>org.springframework.boot</groupId>
    +            <artifactId>spring-boot-maven-plugin</artifactId>
    +        </plugin>
    +    </plugins>
    +</build>
    +
    +<!-- repositories also needed for snapshots and milestones -->
    +
    +
    +
    +

    Then you can create a standard Spring Boot application, like this simple HTTP server:

    +
    +
    +
    +
    @Configuration
    +@EnableAutoConfiguration
    +@RestController
    +public class Application {
    +
    +    @RequestMapping("/")
    +    public String home() {
    +        return "Hello World!";
    +    }
    +
    +    public static void main(String[] args) {
    +        SpringApplication.run(Application.class, args);
    +    }
    +
    +}
    +
    +
    +
    +

    When it runs it will pick up the external configuration from the default local config server on port 8888 if it is running. To modify the startup behaviour you can change the location of the config server using bootstrap.properties (like application.properties but for @@ -491,7 +585,7 @@ the bootstrap phase of an application context), e.g.

    -
    spring.platform.config.uri: http://myconfigserver.com
    +
    spring.cloud.config.uri: http://myconfigserver.com
    @@ -516,46 +610,450 @@ repository>/<file name>" contains the property "foo" with value "bar" and is highest priority).

    + + +
    +

    Spring Cloud Config Server

    +
    +
    +

    The Server provides an HTTP, resource-based API for external +configuration (name-value pairs, or equivalent YAML content). The +server is easily embeddable in a Spring Boot application using the +@EnableConfigServer annotation.

    +
    -

    Sample Application

    +

    Security

    -

    There is a sample application -here. It -is a Spring Boot application so you can run it using the usual -mechanisms (for instance "mvn spring-boot:run"). When it runs it will -look for the config server on "http://localhost:8888" by default, so -you could run the server as well to see it all working together.

    +

    You are free to secure your Config Server in any way that makes sense +to you (from physical network security to OAuth2 bearer +tokens), and Spring Security and Spring Boot make it easy to do pretty +much anything.

    -

    The sample has a test case where the config server is also started in -the same JVM (with a different port), and the test asserts that an -environment property from the git configuration repo is present. To -change the location of the config server just set -"spring.platform.config.uri" in "bootstrap.yml" (or via System -properties etc.).

    +

    To use the default Spring Boot configured HTTP Basic security, just +include Spring Security on the classpath (e.g. through +spring-boot-starter-security). The default is a username of "user" +and a randomly generated password, which isn’t going to be very useful +in practice, so we recommend you configure the password (via +security.user.password) and encrypt it (see below for instructions +on how to do that).

    +
    +
    +
    +

    Encryption and Decryption

    +
    + + + + + +
    +
    Important
    +
    +Prerequisites: to use the encryption and decryption features +you need the full-strength JCE installed in your JVM (it’s not there by default). +You can download the "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files" +from Oracle, and follow instructions for installation (essentially replace the 2 policy files +in the JRE lib/security directory with the ones that you downloaded). +
    -

    The test case has a main() method that runs the server in the same -way (watch the logs for its port), so you can run the whole system in -one process and play with it (e.g. right click on the main in your IDE -and run it). The main() method uses target/config for the working -directory of the git repository, so you can make local changes there -and see them reflected in the running app.

    +

    The server exposes /encrypt and /decrypt endpoints (on the +assumption that these will be secured and only accessed by authorized +agents). If the remote property sources contain encryted content +(values starting with {cipher}) they will be decrypted before +sending to clients over HTTP. The main advantage of this set up is +that the property values don’t have to be in plain text when they are +"at rest" (e.g. in a git repository). If a value cannot be decrypted +it is replaced with an empty string, largely to prevent cipher text +being used as a password in Spring Boot autconfigured HTTP basic.

    +
    +
    +

    If you are setting up a remote config repository for config client +applications it might contain an application.yml like this, for +instance:

    +
    +
    +
    application.yml
    +
    +
    spring:
    +  datasource:
    +    username: dbuser
    +    password: {cipher}FKSAJDFGYOS8F7GLHAKERGFHLSAJ
    +
    +
    +
    +

    You can safely push this plain text to a shared git repository and the +secret password is protected.

    +
    +
    +

    If you are editing a remote config file you can use the Config Server +to encrypt values by POSTing to the /encrypt endpoint, e.g.

    -
    $ curl localhost:8080/env/foo
    -bar
    -$ vi target/config/bar.properties
    -.. change value of "foo", optionally commit
    -$ curl localhost:8080/refresh
    -["foo"]
    -$ curl localhost:8080/env/foo
    -baz
    +
    $ curl localhost:8888/encrypt -d mysecret
    +682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
    -

    The refresh endpoint reports that the "foo" property changed.

    +

    The inverse operation is also available via /decrypt (provided the server is +configured with a symmetric key or a full key pair):

    +
    +
    +
    +
    $ curl localhost:8888/edecrypt -d 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
    +mysecret
    +
    +
    +
    +

    Take the encypted value and add the {cipher} prefix before you put +it in the YAML or properties file, and before you commit and push it +to a remote, potentially insecure store.

    +
    +
    +

    The spring command line client (with Spring Cloud CLI extensions +installed) can also be used to encrypt and decrypt, e.g.

    +
    +
    +
    +
    $ spring encrypt mysecret --key foo
    +682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
    +$ spring decrypt --key foo 682bc583f4641835fa2db009355293665d2647dade3375c0ee201de2a49f7bda
    +mysecret
    +
    +
    +
    +

    To use a key in a file (e.g. an RSA public key for encyption) prepend +the key value with "@" and provide the file path, e.g.

    +
    +
    +
    +
    $ spring encrypt mysecret --key @${HOME}/.ssh/id_rsa.pub
    +AQAjPgt3eFZQXwt8tsHAVv/QHiY5sI2dRcR+...
    +
    +
    +
    +
    +

    Key Management

    +
    +

    The Config Server can use a symmetric (shared) key or an asymmetric +one (RSA key pair). The asymmetric choice is superior in terms of +security, but it is often more convenient to use a symmetric key since +it is just a single property value to configure.

    +
    +
    +

    To configure a symmetric key you just need to set encrypt.key to a +secret String (or use an enviroment variable ENCRYPT_KEY to keep it +out of plain text configuration files). You can also POST a key value +to the /key endpoint (but that won’t change any existing encrypted +values in remote repositories).

    +
    +
    +

    To configure an asymmetric key you can either set the key as a +PEM-encoded text value (in encrypt.key), or via a keystore (e.g. as +created by the keytool utility that comes with the JDK). The +keystore properties are encrypt.keyStore.* with * equal to

    +
    +
    +
      +
    • +

      location (a Resource location),

      +
    • +
    • +

      password (to unlock the keystore) and

      +
    • +
    • +

      alias (to identify which key in the store is to be +used).

      +
    • +
    +
    +
    +

    The encryption is done with the public key, and a private key is +needed for decryption. Thus in principle you can configure only the +public key in the server if you only want to do encryption (and are +prepared to decrypt the values yourself locally with the private +key). In practice you might not want to do that because it spreads the +key management process around all the clients, instead of +concentrating it in the server. On the other hand it’s a useful option +if your config server really is relatively insecure and only a +handful of clients need the encrypted properties.

    +
    +
    +
    +

    Creating a Key Store for Testing

    +
    +

    To create a keystore for testing you can do something like this:

    +
    +
    +
    +
    $ keytool -genkeypair -alias mytestkey -keyalg RSA \
    +  -dname "CN=Web Server,OU=Unit,O=Organization,L=City,S=State,C=US" \
    +  -keypass changeme -keystore server.jks -storepass letmein
    +
    +
    +
    +

    Put the server.jks file in the classpath (for instance) and then in +your application.yml for the Config Server:

    +
    +
    +
    +
    encrypt:
    +  keyStore:
    +    location: classpath:/server.jks
    +    alias: mytestkey
    +    password: letmein
    +
    +
    +
    +
    +
    +
    +

    Spring Cloud Config Client

    +
    +
    +

    A Spring Boot application can take immediate advantage of the Spring +Config Server (or other external property sources provided by the +application developer), and it will also pick up some additional +useful features related to Environment change events. When a config +client starts up it binds to the Config Server (via the bootstrap +configuration property spring.cloud.config.uri) and initializes +Spring Environment with remote property sources

    +
    +
    +

    Environment Changes

    +
    +

    The application will listen for an EnvironmentChangedEvent and react +to the change in a couple of standard ways (additional +ApplicationListeners can be added as @Beans by the user in the +normal way). When an EnvironmentChangedEvent is observed it will +have a list of key values that have changed, and the application will +use those to:

    +
    +
    +
      +
    • +

      Re-bind any @ConfigurationProperties beans in the context

      +
    • +
    • +

      Set the logger levels for any properties in logging.level.*

      +
    • +
    +
    +
    +

    This covers a large class of refresh use cases, and you can verify the +changes by visiting the /configprops endpoint (normal Spring Boot +Actuator feature). For instance a DataSource can have its +maxPoolSize changed at runtime (the default DataSource created by +Spring Boot is an @ConfigurationProperties bean) and grow capacity +dynamically. It does not cover another large class of use cases, where +you need more control over the refresh, and where you need a +configuration change to be atomic over the whole +ApplicationContext. To address those concerns we have +@RefreshScope.

    +
    +
    +
    +

    Refresh Scope

    +
    +

    A Spring @Bean that is marked as @RefreshScope will get special +treatment when there is a configuration change. This addresses the +problem of stateful beans that only get their configuration injected +when they are initialized. For instance if a DataSource has open +connections when the database URL is changed via the Environment, we +probably want the holders of those connections to be able to complete +what they are doing. Then the next time someone borrows a connection +from the pool he gets one with the new URL.

    +
    +
    +

    Refresh scope beans are lazy proxies that initialize when they are +used (i.e. when a method is called), and the scope acts as a cache of +initialized values. To force a bean to re-initialize on the next +method call you just need to invalidate its cache entry.

    +
    +
    +

    The RefreshScope is a bean in the context and it has a public method +refreshAll() to refresh all beans in the scope by clearing the +target cache. There is also a refresh(String) method to refresh an +individual bean by name. This functionality is exposed in the +/refresh endpoint (over HTTP or JMX).

    +
    +
    +
    +

    Encryption and Decryption

    +
    +

    The Config Client has an Environment pre-processor for decrypting +property values locally. It follows the same rules as the Config +Server, and has the same external configuration via encrypt.*. Thus +you can use encrypted values in the form {cipher}* and as long as +there is a valid key then they will be decrypted before the main +application context gets the Environment.

    +
    +
    +
    +

    Endpoints

    +
    +

    For a Spring Boot Actuator application there are some additional management endpoints:

    +
    +
    +
      +
    • +

      POST to /env to update the Environment and rebind @ConfigurationProperties and log levels

      +
    • +
    • +

      /refresh for re-loading the boot strap context and refreshing the @RefreshScope beans

      +
    • +
    • +

      /restart for closing the ApplicationContext and restarting it (disabled by default)

      +
    • +
    • +

      /pause and /resume for calling the Lifecycle methods (stop() and start() on the ApplicationContext)

      +
    • +
    +
    +
    +
    +

    The Bootstrap Application Context

    +
    +

    The Config Client operates by creating a "bootstrap" application +context, which is a parent context for the main application. Out of +the box it is responsible for loading configuration properties from +the Config Server, and also decrypting properties in the local +external configuration files. The two contexts share an Environment +which is the source of external properties for any Spring +application. Bootstrap properties are added with high precedence, so +they cannot be overridden by local configuration.

    +
    +
    +

    The bootstrap context uses a different convention for locating +external configuration than the main application context, so instead +of application.yml (or .properties) you use bootstrap.yml, +keeping the external configuration for bootstrap and main context +nicely separate. Example:

    +
    +
    +
    bootstrap.yml
    +
    +
    spring:
    +  application:
    +    name: foo
    +  cloud:
    +    config:
    +      uri: ${SPRING_CONFIG_URI:http://localhost:8888}
    +
    +
    +
    +

    It is a good idea to set the spring.application.name in +bootstrap.yml if your application needs any application-specific +configuration from the server.

    +
    +
    +

    You can disable the bootstrap process completely by setting +spring.platform.bootstrap.enabled=false (e.g. in System properties).

    +
    +
    +
    +

    Customizing the Bootstrap

    +
    +

    The bootstrap context can be trained to do anything you like by adding +entries to /META-INF/spring.factories under the key +org.springframework.cloud.bootstrap.BootstrapConfiguration. This is +a comma-separated list of Spring @Configuration classes which will +be used to create the context. Any beans that you want to be available +to the main application context for autowiring can be created here, +and also there is a special contract for @Beans of type +ApplicationContextInitializer.

    +
    +
    +

    The bootstrap process ends by injecting initializers into the main +SpringApplication instance (i.e. the normal Spring Boot startup +sequence, whether it is running as a standalone app or deployed in an +application server). First a bootstrap context is created from the +classes found in spring.factories and then all @Beans of type +ApplicationContextInitializer are added to the main +SpringApplication before it is started.

    +
    +
    +
    +

    Customizing the Property Sources

    +
    +

    The default property source for external configuration added by the +bootstrap process is the Config Server, but you can add additional +sources by adding beans of type PropertySourceLocator to the +bootstrap context (via spring.factories). You could use this to +insert additional properties from a different server, or from a +database, for instance.

    +
    +
    +

    As an example, consider the following trivial custom locator:

    +
    +
    +
    +
    @Configuration
    +public class CustomPropertySourceLocator implements PropertySourceLocator {
    +
    +    @Override
    +    public PropertySource<?> locate() {
    +        return new MapPropertySource("customProperty",
    +                Collections.<String, Object>singletonMap("property.from.sample.custom.source", "worked as intended"));
    +    }
    +}
    +
    +
    +
    +

    If you create a jar with this class in it and then add a +META-INF/spring.factories containing:

    +
    +
    +
    +
    org.springframework.cloud.bootstrap.BootstrapConfiguration=sample.custom.CustomPropertySourceLocator
    +
    +
    +
    +

    then the "customProperty" PropertySource will show up in any +application that includes that jar on its classpath.

    +
    +
    +
    +

    Security

    +
    +

    If you use HTTP Basic security on the server then clients just need to +know the password (and username if it isn’t the default). You can do +that via the config server URI, or via separate username and password +properties, e.g.

    +
    +
    +
    bootstrap.yml
    +
    +
    spring:
    +  cloud:
    +    config:
    +     uri: https://user:secret@myconfig.mycompany.com
    +
    +
    +
    +

    or

    +
    +
    +
    bootstrap.yml
    +
    +
    spring:
    +  cloud:
    +    config:
    +     uri: https://myconfig.mycompany.com
    +     username: user
    +     password: secret
    +
    +
    +
    +

    The spring.cloud.config.password and spring.cloud.config.username +values override anything that is provided in the URI.

    +
    +
    +

    If you use another form of security you might need to provide a +RestTemplate to the ConfigServicePropertySourceLocator (e.g. by +grabbing it in the bootstrap context and injecting one).

    @@ -563,8 +1061,8 @@ baz
    - \ No newline at end of file +