Update 2010-11-17-Kleibers-Law.adoc

Update 2010-11-17-Kleibers-Law.adoc

= Kleiber's Law
:hp-tags: light bulb, migrated
:published_at: 2010-11-17

Last week, I happened to read about Kleiber's law while browsing through literature on natural evolution. The implications are really fascinating. It establishes a relationship between mass and metabolism as:

\(metabolism = mass^{\frac{3}{4}}\)

Metabolism is ultimately linked to the number of heartbeats since the heart regulates the supply of oxygen. Therefore, \(\mid heartbeat \mid \propto mass\). Curiously, the number of heartbeats per lifetime tends to be constant. This means that bigger/heavier animals tend to have slower metabolism with lower heart rate compared to, say, flies, with faster metabolism.

Ironically, if we have fixed number of heartbeats, wouldn't running/exercising make us die faster? I suppose, the long term benefits outweigh shot term loss.

= Kleiber's Law
:hp-tags: light bulb, migrated

Last week, I happened to read about Kleiber's law while browsing through literature on natural evolution. The implications are really fascinating. It establishes a relationship between mass and metabolism as:

\(metabolism = mass^{\frac{3}{4}}\)

Metabolism is ultimately linked to the number of heartbeats since the heart regulates the supply of oxygen. Therefore, \(\mid heartbeat \mid \propto mass\). Curiously, the number of heartbeats per lifetime tends to be constant. This means that bigger/heavier animals tend to have slower metabolism with lower heart rate compared to, say, flies, with faster metabolism.

Ironically, if we have fixed number of heartbeats, wouldn't running/exercising make us die faster? I suppose, the long term benefits outweigh shot term loss.


Update 2015-10-16-Crimson-Peak.adoc

Update 2015-10-16-Crimson-Peak.adoc

= 猩红山峰 Crimson Peak
:hp-alt-title: Crimson Peak
:published_at: 2015-10-16
:hp-tags: 2015 剧情
:hardbreaks:

image::http://i12.tietuku.com/3a6a071dff411487t.jpg[link="http://i12.tietuku.com/3a6a071dff411487.jpg"]

== 猩红山峰 Crimson Peak (2015)
导演: 吉尔莫·德尔·托罗
编剧: 吉尔莫·德尔·托罗 / 马修·罗宾斯 / 露辛达·考克森
主演: 米娅·华希科沃斯卡 / 汤姆·希德勒斯顿 / 杰西卡·查斯坦 / 查理·汉纳姆 / 吉姆·比弗
类型: 剧情 / 悬疑 / 惊悚
官方网站: http://crimsonpeak-film.com[crimsonpeak-film.com]
制片国家/地区: 美国 / 加拿大
语言: 英语
上映日期: 2015-10-16(美国)
片长: 119分钟
又名: 血色庄园(港) / 腥红山庄(台) / Haunted Peak
IMDb链接: http://www.imdb.com/title/tt2554274[tt2554274]

== 剧情简介
　　伊迪斯（米娅·华希科沃斯卡 Mia Wasikowska 饰）是一名业余恐怖小说作家，某日，她邂逅了英俊的英国发明家托马斯（汤姆·希德勒斯顿 Tom Hiddleston 饰），后者的彬彬有礼温文尔雅很快就吸引了伊迪斯的注意，两人发展迅速。然而，伊迪斯的父亲卡特（吉姆·比弗 Jim Beaver 饰）却对托马斯充满了敌意，坚决反对女儿同他来往。
　　就在这个节骨眼上，卡特意外身亡，伊迪斯毅然决定离开自己的家乡，跟随托马斯来到了冰天雪地的坎布里亚郡，在这里，矗立着一间豪华而又荒凉的别墅，人们称它为“猩红山庄”。迎接伊迪斯和托马斯的，是托马斯的姐姐露希尔（杰西卡·查斯坦 Jessica Chastain 饰），露希尔诡异而又粗暴的态度让伊迪斯感到十分不适。很快，伊迪斯就发现，在这座仿佛具有生命的别墅里，飘荡着数缕游魂，埋藏了数不清的秘密。

== 下载
猩红山峰.Crimson.Peak.2015.BD720P.X264.AAC.English.CHS-ENG.Mp4Ba.mp4
link:magnet:?xt=urn:btih:6ff9d5c12e8dad2be3ecb38826107e333fefbbf0[Magnet]

猩红山峰.Crimson.Peak.2015.BD1080P.X264.AAC.English.CHS-ENG.Mp4Ba.mp4
link:magnet:?xt=urn:btih:152a352ff89c1bae2296b52363aa5bd9f7603618[Magnet]

= 猩红山峰 Crimson Peak
:hp-alt-title: Crimson Peak
:published_at: 2015-10-16
:hp-tags: 2015
:hardbreaks:

image::http://i12.tietuku.com/3a6a071dff411487t.jpg[link="http://i12.tietuku.com/3a6a071dff411487.jpg"]

== 猩红山峰 Crimson Peak (2015)
导演: 吉尔莫·德尔·托罗
编剧: 吉尔莫·德尔·托罗 / 马修·罗宾斯 / 露辛达·考克森
主演: 米娅·华希科沃斯卡 / 汤姆·希德勒斯顿 / 杰西卡·查斯坦 / 查理·汉纳姆 / 吉姆·比弗
类型: 剧情 / 悬疑 / 惊悚
官方网站: http://crimsonpeak-film.com[crimsonpeak-film.com]
制片国家/地区: 美国 / 加拿大
语言: 英语
上映日期: 2015-10-16(美国)
片长: 119分钟
又名: 血色庄园(港) / 腥红山庄(台) / Haunted Peak
IMDb链接: http://www.imdb.com/title/tt2554274[tt2554274]

== 剧情简介
　　伊迪斯（米娅·华希科沃斯卡 Mia Wasikowska 饰）是一名业余恐怖小说作家，某日，她邂逅了英俊的英国发明家托马斯（汤姆·希德勒斯顿 Tom Hiddleston 饰），后者的彬彬有礼温文尔雅很快就吸引了伊迪斯的注意，两人发展迅速。然而，伊迪斯的父亲卡特（吉姆·比弗 Jim Beaver 饰）却对托马斯充满了敌意，坚决反对女儿同他来往。
　　就在这个节骨眼上，卡特意外身亡，伊迪斯毅然决定离开自己的家乡，跟随托马斯来到了冰天雪地的坎布里亚郡，在这里，矗立着一间豪华而又荒凉的别墅，人们称它为“猩红山庄”。迎接伊迪斯和托马斯的，是托马斯的姐姐露希尔（杰西卡·查斯坦 Jessica Chastain 饰），露希尔诡异而又粗暴的态度让伊迪斯感到十分不适。很快，伊迪斯就发现，在这座仿佛具有生命的别墅里，飘荡着数缕游魂，埋藏了数不清的秘密。

== 下载
猩红山峰.Crimson.Peak.2015.BD720P.X264.AAC.English.CHS-ENG.Mp4Ba.mp4
link:magnet:?xt=urn:btih:6ff9d5c12e8dad2be3ecb38826107e333fefbbf0[Magnet]

猩红山峰.Crimson.Peak.2015.BD1080P.X264.AAC.English.CHS-ENG.Mp4Ba.mp4
link:magnet:?xt=urn:btih:152a352ff89c1bae2296b52363aa5bd9f7603618[Magnet]


Update 2019-01-31-Blog-Servers.adoc

Update 2019-01-31-Blog-Servers.adoc

= 博客服务
:hp-image: /covers/cover.png
:published_at: 2019-01-31
:hp-tags: Blog,
:hp-alt-title: Blog Servers

== 世界
* https://www.blogger.com/about/?r=1-null_user[Blogger^]

= 博客服务
:hp-image: /covers/cover.png
:published_at: 2019-01-31
:hp-tags: Blog,
:hp-alt-title: Blog Servers

== 世界
* https://www.blogger.com/about/?r=1-null_user[Blogger]


update all-in-one docs

update all-in-one docs

= all-in-one explained
Crunchy Data Solutions, Inc.
v1.2.6, {docdate}
:title-logo-image: image:crunchy_logo.png["CrunchyData Logo",align="center",scaledwidth="80%"]

== All-In-One Crunchy OSE Image

To allow users to take a look at crunchy on Openshift,
we have created an all-in-one Virtual Machine that you
can run which will include Openshift and the Crunchy
containers.

The image can be imported as an appliance in VirtualBox.

=== configuration

The image uses a NAT interface and Host Only Interface for networking.
The Host Only interface lets us set a static IP of 192.168.56.6, this
address is related to the host's vboxnet0 ip address which by default
is 192.168.56.1 on VirtualBox.

The NAT interface allows the VM access to the internet and allows
for ssh access from the host.

The VM has an entry in /etc/dhcp/dhclient.conf that prepends
the 192.168.56.6 address as a nameserver.  This allows us to
do DNS discovery using the Openshift DNS container which
binds to 192.168.56.6:53.

The image includes a Gnome UI environment if you want to use that,
especially useful for using the Openshift web console.

The VM instance has a *crunchy* user created with a password of *crunchy*.
This user has sudo privs.

=== Openshift 3.3

Openshift 3.3 is installed and running when you start the VM.

There are 10 small PVs already created and each is hosted on
a local NFS /nfsfileshare.

The openshift is configured to allow any user to log in.  The *crunchy*
user is already created, a password of *crunchy* is also specified.  An
openshift project named *crunchy* is already created as well.

The *crunchy* openshift user has been granted openshift cluster-admin
privs which allows it to do pretty much anything including
creating PVs.

You can start Gnome by logging in and typing:
....
startx
....

Firefox is installed and the openshift console is available at https://10.0.2.15:8443/console

=== Crunchy Images

The crunchy version 1.2.6 container images are preinstalled
in the VM instance.

=== Crunchy Examples

The crunchy container examples for openshift are installed
here:

==== $BUILDBASE/examples/openshift
This directory contains all sorts of examples that create pods
and services when you run the run.sh script in each subdirectory.

==== $BUILDBASE/examples/dedicated
This directory contains openshift templates that are pre-installed
into the Openshift environment, with these you can click on Add To Project
in the web console to deploy containers.

== Legal Notices

Copyright © 2016 Crunchy Data Solutions, Inc.

CRUNCHY DATA SOLUTIONS, INC. PROVIDES THIS GUIDE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF NON INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.

Crunchy, Crunchy Data Solutions, Inc. and the Crunchy Hippo Logo are trademarks of Crunchy Data Solutions, Inc.

= all-in-one explained
Crunchy Data Solutions, Inc.
v1.2.6, {docdate}
:title-logo-image: image:crunchy_logo.png["CrunchyData Logo",align="center",scaledwidth="80%"]

== All-In-One Crunchy OSE Image

To allow users to take a look at crunchy on Openshift,
we have created an all-in-one Virtual Machine that you
can run which will include Openshift and the Crunchy
containers.

The image can be imported as an appliance in VirtualBox.

=== configuration

The image uses a NAT interface for networking.

The image includes a Gnome UI environment if you want to use that,
especially useful for using the Openshift web console.

The VM instance has a *crunchy* user created with a password of *crunchy*.
This user has sudo privs.

=== Openshift 3.3

Openshift 3.3 is installed and running when you start the VM.

There are 10 small PVs already created and each is hosted on
a local NFS /nfsfileshare.

The openshift is configured to allow any user to log in.  The *crunchy*
user is already created, a password of *crunchy* is also specified.  An
openshift project named *crunchy* is already created as well.

The *crunchy* openshift user has been granted openshift cluster-admin
privs which allows it to do pretty much anything including
creating PVs.

You can start Gnome by logging in and typing:
....
startx
....

Firefox is installed and the openshift console is available at https://10.0.2.15:8443/console

=== Crunchy Images

The crunchy version 1.2.6 container images are preinstalled
in the VM instance.

=== Crunchy Examples

The crunchy container examples for openshift are installed
here:

==== $BUILDBASE/examples/openshift
This directory contains all sorts of examples that create pods
and services when you run the run.sh script in each subdirectory.

==== $BUILDBASE/examples/dedicated
This directory contains openshift templates that are pre-installed
into the Openshift environment, with these you can click on Add To Project
in the web console to deploy containers.

== Legal Notices

Copyright © 2016 Crunchy Data Solutions, Inc.

CRUNCHY DATA SOLUTIONS, INC. PROVIDES THIS GUIDE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF NON INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.

Crunchy, Crunchy Data Solutions, Inc. and the Crunchy Hippo Logo are trademarks of Crunchy Data Solutions, Inc.


Added Chrome 74.0 image information

Added Chrome 74.0 image information

== Browser Image information
=== Firefox

.Firefox Images with Selenium Server
|===
| Image | VNC Image | Selenium Version | Firefox Version | Client Version

| selenoid/firefox:3.6 | selenoid/vnc:firefox_3.6 | 2.20.0 | 3.6.16 i386 (dialogs may not work) .7+<.^|
**Java:** 2.53.1 and below
**Python:** not supported
**selenium-webdriver.js:** not supported
| selenoid/firefox:4.0 | selenoid/vnc:firefox_4.0 | 2.20.0 | 4.0.1 i386
| selenoid/firefox:5.0 | selenoid/vnc:firefox_5.0 | 2.20.0 | 5.0.1 i386
| selenoid/firefox:6.0 | selenoid/vnc:firefox_6.0 | 2.20.0 | 6.0.2 i386
| selenoid/firefox:7.0 | selenoid/vnc:firefox_7.0 | 2.20.0 | 7.0.1 i386
| selenoid/firefox:8.0 | selenoid/vnc:firefox_8.0 | 2.20.0 | 8.0.1 i386
| selenoid/firefox:9.0 | selenoid/vnc:firefox_9.0 | 2.20.0 | 9.0.1
| selenoid/firefox:10.0 | selenoid/vnc:firefox_10.0 | 2.32.0 | 10.0.2 .13+<.^|
**Java:** any modern version
**Python:** not supported
**selenium-webdriver.js:** not supported
| selenoid/firefox:11.0 | selenoid/vnc:firefox_11.0 | 2.32.0 | 11.0
| selenoid/firefox:12.0 | selenoid/vnc:firefox_12.0 | 2.32.0 | 12.0
| selenoid/firefox:13.0 | selenoid/vnc:firefox_13.0 | 2.32.0 | 13.0
| selenoid/firefox:14.0 | selenoid/vnc:firefox_14.0 | 2.32.0 | 14.0.1
| selenoid/firefox:15.0 | selenoid/vnc:firefox_15.0 | 2.32.0 | 15.0.1
| selenoid/firefox:16.0 | selenoid/vnc:firefox_16.0 | 2.32.0 | 16.0.2
| selenoid/firefox:17.0 | selenoid/vnc:firefox_17.0 | 2.32.0 | 17.0.1
| selenoid/firefox:18.0 | selenoid/vnc:firefox_18.0 | 2.32.0 | 18.0.2
| selenoid/firefox:19.0 | selenoid/vnc:firefox_19.0 | 2.32.0 | 19.0.2
| selenoid/firefox:20.0 | selenoid/vnc:firefox_20.0 | 2.32.0 | 20.0
| selenoid/firefox:21.0 | selenoid/vnc:firefox_21.0 | 2.32.0 | 21.0
| selenoid/firefox:22.0 | selenoid/vnc:firefox_22.0 | 2.32.0 | 22.0
| selenoid/firefox:23.0 | selenoid/vnc:firefox_23.0 | 2.35.0 | 23.0.1 .25+<.^| Any modern client version
| selenoid/firefox:24.0 | selenoid/vnc:firefox_24.0 | 2.39.0 | 24.0
| selenoid/firefox:25.0 | selenoid/vnc:firefox_25.0 | 2.39.0 | 25.0.1
| selenoid/firefox:26.0 | selenoid/vnc:firefox_26.0 | 2.39.0 | 26.0
| selenoid/firefox:27.0 | selenoid/vnc:firefox_27.0 | 2.40.0 | 27.0.1
| selenoid/firefox:28.0 | selenoid/vnc:firefox_28.0 | 2.41.0 | 28.0
| selenoid/firefox:29.0 | selenoid/vnc:firefox_29.0 | 2.43.1 | 29.0.1
| selenoid/firefox:30.0 | selenoid/vnc:firefox_30.0 | 2.43.1 | 30.0
| selenoid/firefox:31.0 | selenoid/vnc:firefox_31.0 | 2.44.0 | 31.0
| selenoid/firefox:32.0 | selenoid/vnc:firefox_32.0 | 2.44.0 | 32.0.3
| selenoid/firefox:33.0 | selenoid/vnc:firefox_33.0 | 2.44.0 | 33.0.3
| selenoid/firefox:34.0 | selenoid/vnc:firefox_34.0 | 2.45.0 | 34.0.5
| selenoid/firefox:35.0 | selenoid/vnc:firefox_35.0 | 2.45.0 | 35.0.1
| selenoid/firefox:36.0 | selenoid/vnc:firefox_36.0 | 2.45.0 | 36.0.1
| selenoid/firefox:37.0 | selenoid/vnc:firefox_37.0 | 2.45.0 | 37.0.2
| selenoid/firefox:38.0 | selenoid/vnc:firefox_38.0 | 2.45.0 | 38.0.5
| selenoid/firefox:39.0 | selenoid/vnc:firefox_39.0 | 2.45.0 | 39.0.3
| selenoid/firefox:40.0 | selenoid/vnc:firefox_40.0 | 2.45.0 | 40.0.3
| selenoid/firefox:41.0 | selenoid/vnc:firefox_41.0 | 2.45.0 | 41.0.2
| selenoid/firefox:42.0 | selenoid/vnc:firefox_42.0 | 2.47.1 | 42.0
| selenoid/firefox:43.0 | selenoid/vnc:firefox_43.0 | 2.53.1 | 43.0.4
| selenoid/firefox:44.0 | selenoid/vnc:firefox_44.0 | 2.53.1 | 44.0.2
| selenoid/firefox:45.0 | selenoid/vnc:firefox_45.0 | 2.53.1 | 45.0.2
| selenoid/firefox:46.0 | selenoid/vnc:firefox_46.0 | 2.53.1 | 46.0.1
| selenoid/firefox:47.0 | selenoid/vnc:firefox_47.0 | 2.53.1 | 47.0.1
|===

WARNING: Firefox 53.0+ images require Selenium client 3.4.0 or newer.

.Firefox Images with Selenoid
|===
| Image | VNC Image | Selenoid Version | Geckodriver Version | Firefox Version | Client Version

| selenoid/firefox:48.0 | selenoid/vnc:firefox_48.0 | 1.3.9 | 0.13.0 | 48.0.2 (page load timeout, native events and proxies don't work) .19+<.^|
**Java, selenium-webdriver.js**: 3.4.0 and above
**Python**: 3.5.0 and above
| selenoid/firefox:49.0 | selenoid/vnc:firefox_49.0 | 1.3.9 | 0.13.0 | 49.0.2 (page load timeout, native events and switching between windows don't work)
| selenoid/firefox:50.0 | selenoid/vnc:firefox_50.0 | 1.3.9 | 0.13.0 | 50.0.2 (page load timeout, native events, switching windows and proxies don't work)
| selenoid/firefox:51.0 | selenoid/vnc:firefox_51.0 | 1.3.9 | 0.14.0 | 51.0.1 (page load timeout, native events, switching windows and proxies don't work)
| selenoid/firefox:52.0 | selenoid/vnc:firefox_52.0 | 1.3.9 | 0.15.0 | 52.0.2 (page load timeout, native events, switching windows and proxies don't work)
| selenoid/firefox:53.0 | selenoid/vnc:firefox_53.0 | 1.3.9 | 0.16.0 | 53.0.2 (switching windows may not work)
| selenoid/firefox:54.0 | selenoid/vnc:firefox_54.0 | 1.3.9 | 0.17.0 | 54.0.1 (switching windows may not work)
| selenoid/firefox:55.0 | selenoid/vnc:firefox_55.0 | 1.3.9 | 0.18.0 | 55.0.1 (switching windows may not work)
| selenoid/firefox:56.0 | selenoid/vnc:firefox_56.0 | 1.3.9 | 0.19.1 | 56.0.1
| selenoid/firefox:57.0 | selenoid/vnc:firefox_57.0 | 1.3.9 | 0.19.1 | 57.0
| selenoid/firefox:58.0 | selenoid/vnc:firefox_58.0 | 1.6.0 | 0.20.1 | 58.0
| selenoid/firefox:59.0 | selenoid/vnc:firefox_59.0 | 1.6.0 | 0.20.1 | 59.0.1
| selenoid/firefox:60.0 | selenoid/vnc:firefox_60.0 | 1.6.2 | 0.21.0 | 60.0.2
| selenoid/firefox:61.0 | selenoid/vnc:firefox_61.0 | 1.6.2 | 0.21.0 | 61.0
| selenoid/firefox:62.0 | selenoid/vnc:firefox_62.0 | 1.7.2 | 0.22.0 | 62.0
| selenoid/firefox:63.0 | selenoid/vnc:firefox_63.0 | 1.8.1 | 0.23.0 | 63.0
| selenoid/firefox:64.0 | selenoid/vnc:firefox_64.0 | 1.8.4 | 0.23.0 | 64.0
| selenoid/firefox:65.0 | selenoid/vnc:firefox_65.0 | 1.9.0 | 0.24.0 | 65.0
| selenoid/firefox:66.0 | selenoid/vnc:firefox_66.0 | 1.9.1 | 0.24.0 | 66.0.1
|===

=== Chrome

.Chrome Images
|===
| Image | VNC Image | Chromedriver version | Chrome version

| selenoid/chrome:48.0 | selenoid/vnc:chrome_48.0 | 2.21 | 48.0.2564.116
| selenoid/chrome:49.0 | selenoid/vnc:chrome_49.0 | 2.22 | 49.0.2623.112
| selenoid/chrome:50.0 | selenoid/vnc:chrome_50.0 | 2.22 | 50.0.2661.102
| selenoid/chrome:51.0 | selenoid/vnc:chrome_51.0 | 2.23 | 51.0.2704.106
| selenoid/chrome:52.0 | selenoid/vnc:chrome_52.0 | 2.24 | 52.0.2743.116
| selenoid/chrome:53.0 | selenoid/vnc:chrome_53.0 | 2.26 | 53.0.2785.143
| selenoid/chrome:54.0 | selenoid/vnc:chrome_54.0 | 2.27 | 54.0.2840.100
| selenoid/chrome:55.0 | selenoid/vnc:chrome_55.0 | 2.28 | 55.0.2883.87
| selenoid/chrome:56.0 | selenoid/vnc:chrome_56.0 | 2.29 | 56.0.2924.87
| selenoid/chrome:57.0 | selenoid/vnc:chrome_57.0 | 2.29 | 57.0.2987.110
| selenoid/chrome:58.0 | selenoid/vnc:chrome_58.0 | 2.29 | 58.0.3029.81
| selenoid/chrome:59.0 | selenoid/vnc:chrome_59.0 | 2.30 | 59.0.3071.86
| selenoid/chrome:60.0 | selenoid/vnc:chrome_60.0 | 2.31 | 60.0.3112.90
| selenoid/chrome:61.0 | selenoid/vnc:chrome_61.0 | 2.32 | 61.0.3163.79
| selenoid/chrome:62.0 | selenoid/vnc:chrome_62.0 | 2.33 | 62.0.3202.62
| selenoid/chrome:63.0 | selenoid/vnc:chrome_63.0 | 2.33 | 63.0.3239.84
| selenoid/chrome:64.0 | selenoid/vnc:chrome_64.0 | 2.35 | 64.0.3282.119
| selenoid/chrome:65.0 | selenoid/vnc:chrome_65.0 | 2.38 | 65.0.3325.181
| selenoid/chrome:66.0 | selenoid/vnc:chrome_66.0 | 2.38 | 66.0.3359.117
| selenoid/chrome:67.0 | selenoid/vnc:chrome_67.0 | 2.39 | 67.0.3396.62
| selenoid/chrome:68.0 | selenoid/vnc:chrome_68.0 | 2.41 | 68.0.3440.106
| selenoid/chrome:69.0 | selenoid/vnc:chrome_69.0 | 2.42 | 69.0.3497.100
| selenoid/chrome:70.0 | selenoid/vnc:chrome_70.0 | 2.44 | 70.0.3538.110
| selenoid/chrome:71.0 | selenoid/vnc:chrome_71.0 | 2.44 | 71.0.3578.80
| selenoid/chrome:72.0 | selenoid/vnc:chrome_72.0 | 2.46 | 72.0.3626.121
| selenoid/chrome:73.0 | selenoid/vnc:chrome_73.0 | 73.0.3683.68 | 73.0.3683.75
| selenoid/chrome:74.0 | selenoid/vnc:chrome_74.0 | 74.0.3729.108 | 74.0.3729.6
|===

[NOTE]
====
. These images work with any modern Selenium client version.
. Images for older Chrome versions were not built because we have no Debian packages. If you have such packages - we could create more images.
====

=== Opera

.Opera Presto Images
|===
| Image | VNC Image | Selenium version | Opera version

| selenoid/opera:12.16 | selenoid/vnc:opera_12.16 | 2.37.0 | 12.16.1860 (dialogs and probably async JS don't work)
|===

[WARNING]
====
Due to bug in *Operadriver* to work with *Opera Blink* images you need to pass additional capability:
[source,javascript]
{"browserName": "opera", "operaOptions": {"binary": "/usr/bin/opera"}}

We do not consider these images really stable. Many of base operations like working with proxies may not work.
====

.Opera Blink Images
|===
| Image | VNC Image | Operadriver version | Opera version

| selenoid/opera:33.0 | selenoid/vnc:opera_33.0 | 0.2.2 | 33.0.1990.115
| selenoid/opera:34.0 | selenoid/vnc:opera_34.0 | 0.2.2 | 34.0.2036.50
| selenoid/opera:35.0 | selenoid/vnc:opera_35.0 | 0.2.2 | 35.0.2066.92
| selenoid/opera:36.0 | selenoid/vnc:opera_36.0 | 0.2.2 | 36.0.2130.65
| selenoid/opera:37.0 | selenoid/vnc:opera_37.0 | 0.2.2 | 37.0.2178.54
| selenoid/opera:38.0 | selenoid/vnc:opera_38.0 | 0.2.2 | 38.0.2220.41
| selenoid/opera:39.0 | selenoid/vnc:opera_39.0 | 0.2.2 | 39.0.2256.71
| selenoid/opera:40.0 | selenoid/vnc:opera_40.0 | 0.2.2 | 40.0.2308.90
| selenoid/opera:41.0 | selenoid/vnc:opera_41.0 | 2.27 | 41.0.2353.69
| selenoid/opera:42.0 | selenoid/vnc:opera_42.0 | 2.27 | 42.0.2393.94
| selenoid/opera:43.0 | selenoid/vnc:opera_43.0 | 2.27 | 43.0.2442.991
| selenoid/opera:44.0 | selenoid/vnc:opera_44.0 | 2.27 | 44.0.2510.857
| selenoid/opera:45.0 | selenoid/vnc:opera_45.0 | 2.27 | 45.0.2552.635
| selenoid/opera:46.0 | selenoid/vnc:opera_46.0 | 2.27 | 46.0.2597.26
| selenoid/opera:47.0 | selenoid/vnc:opera_47.0 | 2.29 | 47.0.2631.39
| selenoid/opera:48.0 | selenoid/vnc:opera_48.0 | 2.30 | 48.0.2685.35
| selenoid/opera:49.0 | selenoid/vnc:opera_49.0 | 2.32 | 49.0.2725.39
| selenoid/opera:50.0 | selenoid/vnc:opera_50.0 | 2.32 | 50.0.2762.45
| selenoid/opera:51.0 | selenoid/vnc:opera_51.0 | 2.33 | 51.0.2830.26
| selenoid/opera:52.0 | selenoid/vnc:opera_52.0 | 2.35 | 52.0.2871.37
| selenoid/opera:53.0 | selenoid/vnc:opera_53.0 | 2.36 | 53.0.2907.68
| selenoid/opera:54.0 | selenoid/vnc:opera_54.0 | 2.37 | 54.0.2952.46
| selenoid/opera:55.0 | selenoid/vnc:opera_55.0 | 2.37 | 55.0.2994.37
| selenoid/opera:56.0 | selenoid/vnc:opera_56.0 | 2.40 | 56.0.3051.31
| selenoid/opera:57.0 | selenoid/vnc:opera_57.0 | 2.41 | 57.0.3098.76
| selenoid/opera:58.0 | selenoid/vnc:opera_58.0 | 2.42 | 58.0.3135.79
| - | - | - | 59.0.x.x (no stable release exists)
| selenoid/opera:60.0 | selenoid/vnc:opera_60.0 | 2.45 | 60.0.3255.56
|===

[NOTE]
====
. These images work with any modern Selenium client version.
. Images for older Opera versions were not built because we have no Debian packages. If you have such packages - we could create more images.
====

=== Android

WARNING: Hardware server or virtual machine with nested virtualization support is required to run Android images.

.Android Images
|===
| Image | Android version | Appium version

| selenoid/android:4.4 | 4.4 | 1.8.1
| selenoid/android:5.1 | 5.1 | 1.8.1
| selenoid/android:6.0 | 6.0 | 1.8.1
| selenoid/android:7.0 | 7.0 | 1.8.1
| selenoid/android:7.1 | 7.1 | 1.8.1
| selenoid/android:8.0 | 8.0 | 1.8.1
| selenoid/android:8.1 | 8.1 | 1.8.1
|===

[NOTE]
====
. These images include VNC server and Android Quick Boot snapshot.
. Neither Chromedriver nor Chrome Mobile are installed. To test hybrid apps build your own image using provided automation script.
====

== Browser Image information
=== Firefox

.Firefox Images with Selenium Server
|===
| Image | VNC Image | Selenium Version | Firefox Version | Client Version

| selenoid/firefox:3.6 | selenoid/vnc:firefox_3.6 | 2.20.0 | 3.6.16 i386 (dialogs may not work) .7+<.^|
**Java:** 2.53.1 and below
**Python:** not supported
**selenium-webdriver.js:** not supported
| selenoid/firefox:4.0 | selenoid/vnc:firefox_4.0 | 2.20.0 | 4.0.1 i386
| selenoid/firefox:5.0 | selenoid/vnc:firefox_5.0 | 2.20.0 | 5.0.1 i386
| selenoid/firefox:6.0 | selenoid/vnc:firefox_6.0 | 2.20.0 | 6.0.2 i386
| selenoid/firefox:7.0 | selenoid/vnc:firefox_7.0 | 2.20.0 | 7.0.1 i386
| selenoid/firefox:8.0 | selenoid/vnc:firefox_8.0 | 2.20.0 | 8.0.1 i386
| selenoid/firefox:9.0 | selenoid/vnc:firefox_9.0 | 2.20.0 | 9.0.1
| selenoid/firefox:10.0 | selenoid/vnc:firefox_10.0 | 2.32.0 | 10.0.2 .13+<.^|
**Java:** any modern version
**Python:** not supported
**selenium-webdriver.js:** not supported
| selenoid/firefox:11.0 | selenoid/vnc:firefox_11.0 | 2.32.0 | 11.0
| selenoid/firefox:12.0 | selenoid/vnc:firefox_12.0 | 2.32.0 | 12.0
| selenoid/firefox:13.0 | selenoid/vnc:firefox_13.0 | 2.32.0 | 13.0
| selenoid/firefox:14.0 | selenoid/vnc:firefox_14.0 | 2.32.0 | 14.0.1
| selenoid/firefox:15.0 | selenoid/vnc:firefox_15.0 | 2.32.0 | 15.0.1
| selenoid/firefox:16.0 | selenoid/vnc:firefox_16.0 | 2.32.0 | 16.0.2
| selenoid/firefox:17.0 | selenoid/vnc:firefox_17.0 | 2.32.0 | 17.0.1
| selenoid/firefox:18.0 | selenoid/vnc:firefox_18.0 | 2.32.0 | 18.0.2
| selenoid/firefox:19.0 | selenoid/vnc:firefox_19.0 | 2.32.0 | 19.0.2
| selenoid/firefox:20.0 | selenoid/vnc:firefox_20.0 | 2.32.0 | 20.0
| selenoid/firefox:21.0 | selenoid/vnc:firefox_21.0 | 2.32.0 | 21.0
| selenoid/firefox:22.0 | selenoid/vnc:firefox_22.0 | 2.32.0 | 22.0
| selenoid/firefox:23.0 | selenoid/vnc:firefox_23.0 | 2.35.0 | 23.0.1 .25+<.^| Any modern client version
| selenoid/firefox:24.0 | selenoid/vnc:firefox_24.0 | 2.39.0 | 24.0
| selenoid/firefox:25.0 | selenoid/vnc:firefox_25.0 | 2.39.0 | 25.0.1
| selenoid/firefox:26.0 | selenoid/vnc:firefox_26.0 | 2.39.0 | 26.0
| selenoid/firefox:27.0 | selenoid/vnc:firefox_27.0 | 2.40.0 | 27.0.1
| selenoid/firefox:28.0 | selenoid/vnc:firefox_28.0 | 2.41.0 | 28.0
| selenoid/firefox:29.0 | selenoid/vnc:firefox_29.0 | 2.43.1 | 29.0.1
| selenoid/firefox:30.0 | selenoid/vnc:firefox_30.0 | 2.43.1 | 30.0
| selenoid/firefox:31.0 | selenoid/vnc:firefox_31.0 | 2.44.0 | 31.0
| selenoid/firefox:32.0 | selenoid/vnc:firefox_32.0 | 2.44.0 | 32.0.3
| selenoid/firefox:33.0 | selenoid/vnc:firefox_33.0 | 2.44.0 | 33.0.3
| selenoid/firefox:34.0 | selenoid/vnc:firefox_34.0 | 2.45.0 | 34.0.5
| selenoid/firefox:35.0 | selenoid/vnc:firefox_35.0 | 2.45.0 | 35.0.1
| selenoid/firefox:36.0 | selenoid/vnc:firefox_36.0 | 2.45.0 | 36.0.1
| selenoid/firefox:37.0 | selenoid/vnc:firefox_37.0 | 2.45.0 | 37.0.2
| selenoid/firefox:38.0 | selenoid/vnc:firefox_38.0 | 2.45.0 | 38.0.5
| selenoid/firefox:39.0 | selenoid/vnc:firefox_39.0 | 2.45.0 | 39.0.3
| selenoid/firefox:40.0 | selenoid/vnc:firefox_40.0 | 2.45.0 | 40.0.3
| selenoid/firefox:41.0 | selenoid/vnc:firefox_41.0 | 2.45.0 | 41.0.2
| selenoid/firefox:42.0 | selenoid/vnc:firefox_42.0 | 2.47.1 | 42.0
| selenoid/firefox:43.0 | selenoid/vnc:firefox_43.0 | 2.53.1 | 43.0.4
| selenoid/firefox:44.0 | selenoid/vnc:firefox_44.0 | 2.53.1 | 44.0.2
| selenoid/firefox:45.0 | selenoid/vnc:firefox_45.0 | 2.53.1 | 45.0.2
| selenoid/firefox:46.0 | selenoid/vnc:firefox_46.0 | 2.53.1 | 46.0.1
| selenoid/firefox:47.0 | selenoid/vnc:firefox_47.0 | 2.53.1 | 47.0.1
|===

WARNING: Firefox 53.0+ images require Selenium client 3.4.0 or newer.

.Firefox Images with Selenoid
|===
| Image | VNC Image | Selenoid Version | Geckodriver Version | Firefox Version | Client Version

| selenoid/firefox:48.0 | selenoid/vnc:firefox_48.0 | 1.3.9 | 0.13.0 | 48.0.2 (page load timeout, native events and proxies don't work) .19+<.^|
**Java, selenium-webdriver.js**: 3.4.0 and above
**Python**: 3.5.0 and above
| selenoid/firefox:49.0 | selenoid/vnc:firefox_49.0 | 1.3.9 | 0.13.0 | 49.0.2 (page load timeout, native events and switching between windows don't work)
| selenoid/firefox:50.0 | selenoid/vnc:firefox_50.0 | 1.3.9 | 0.13.0 | 50.0.2 (page load timeout, native events, switching windows and proxies don't work)
| selenoid/firefox:51.0 | selenoid/vnc:firefox_51.0 | 1.3.9 | 0.14.0 | 51.0.1 (page load timeout, native events, switching windows and proxies don't work)
| selenoid/firefox:52.0 | selenoid/vnc:firefox_52.0 | 1.3.9 | 0.15.0 | 52.0.2 (page load timeout, native events, switching windows and proxies don't work)
| selenoid/firefox:53.0 | selenoid/vnc:firefox_53.0 | 1.3.9 | 0.16.0 | 53.0.2 (switching windows may not work)
| selenoid/firefox:54.0 | selenoid/vnc:firefox_54.0 | 1.3.9 | 0.17.0 | 54.0.1 (switching windows may not work)
| selenoid/firefox:55.0 | selenoid/vnc:firefox_55.0 | 1.3.9 | 0.18.0 | 55.0.1 (switching windows may not work)
| selenoid/firefox:56.0 | selenoid/vnc:firefox_56.0 | 1.3.9 | 0.19.1 | 56.0.1
| selenoid/firefox:57.0 | selenoid/vnc:firefox_57.0 | 1.3.9 | 0.19.1 | 57.0
| selenoid/firefox:58.0 | selenoid/vnc:firefox_58.0 | 1.6.0 | 0.20.1 | 58.0
| selenoid/firefox:59.0 | selenoid/vnc:firefox_59.0 | 1.6.0 | 0.20.1 | 59.0.1
| selenoid/firefox:60.0 | selenoid/vnc:firefox_60.0 | 1.6.2 | 0.21.0 | 60.0.2
| selenoid/firefox:61.0 | selenoid/vnc:firefox_61.0 | 1.6.2 | 0.21.0 | 61.0
| selenoid/firefox:62.0 | selenoid/vnc:firefox_62.0 | 1.7.2 | 0.22.0 | 62.0
| selenoid/firefox:63.0 | selenoid/vnc:firefox_63.0 | 1.8.1 | 0.23.0 | 63.0
| selenoid/firefox:64.0 | selenoid/vnc:firefox_64.0 | 1.8.4 | 0.23.0 | 64.0
| selenoid/firefox:65.0 | selenoid/vnc:firefox_65.0 | 1.9.0 | 0.24.0 | 65.0
| selenoid/firefox:66.0 | selenoid/vnc:firefox_66.0 | 1.9.1 | 0.24.0 | 66.0.1
|===

=== Chrome

.Chrome Images
|===
| Image | VNC Image | Chromedriver version | Chrome version

| selenoid/chrome:48.0 | selenoid/vnc:chrome_48.0 | 2.21 | 48.0.2564.116
| selenoid/chrome:49.0 | selenoid/vnc:chrome_49.0 | 2.22 | 49.0.2623.112
| selenoid/chrome:50.0 | selenoid/vnc:chrome_50.0 | 2.22 | 50.0.2661.102
| selenoid/chrome:51.0 | selenoid/vnc:chrome_51.0 | 2.23 | 51.0.2704.106
| selenoid/chrome:52.0 | selenoid/vnc:chrome_52.0 | 2.24 | 52.0.2743.116
| selenoid/chrome:53.0 | selenoid/vnc:chrome_53.0 | 2.26 | 53.0.2785.143
| selenoid/chrome:54.0 | selenoid/vnc:chrome_54.0 | 2.27 | 54.0.2840.100
| selenoid/chrome:55.0 | selenoid/vnc:chrome_55.0 | 2.28 | 55.0.2883.87
| selenoid/chrome:56.0 | selenoid/vnc:chrome_56.0 | 2.29 | 56.0.2924.87
| selenoid/chrome:57.0 | selenoid/vnc:chrome_57.0 | 2.29 | 57.0.2987.110
| selenoid/chrome:58.0 | selenoid/vnc:chrome_58.0 | 2.29 | 58.0.3029.81
| selenoid/chrome:59.0 | selenoid/vnc:chrome_59.0 | 2.30 | 59.0.3071.86
| selenoid/chrome:60.0 | selenoid/vnc:chrome_60.0 | 2.31 | 60.0.3112.90
| selenoid/chrome:61.0 | selenoid/vnc:chrome_61.0 | 2.32 | 61.0.3163.79
| selenoid/chrome:62.0 | selenoid/vnc:chrome_62.0 | 2.33 | 62.0.3202.62
| selenoid/chrome:63.0 | selenoid/vnc:chrome_63.0 | 2.33 | 63.0.3239.84
| selenoid/chrome:64.0 | selenoid/vnc:chrome_64.0 | 2.35 | 64.0.3282.119
| selenoid/chrome:65.0 | selenoid/vnc:chrome_65.0 | 2.38 | 65.0.3325.181
| selenoid/chrome:66.0 | selenoid/vnc:chrome_66.0 | 2.38 | 66.0.3359.117
| selenoid/chrome:67.0 | selenoid/vnc:chrome_67.0 | 2.39 | 67.0.3396.62
| selenoid/chrome:68.0 | selenoid/vnc:chrome_68.0 | 2.41 | 68.0.3440.106
| selenoid/chrome:69.0 | selenoid/vnc:chrome_69.0 | 2.42 | 69.0.3497.100
| selenoid/chrome:70.0 | selenoid/vnc:chrome_70.0 | 2.44 | 70.0.3538.110
| selenoid/chrome:71.0 | selenoid/vnc:chrome_71.0 | 2.44 | 71.0.3578.80
| selenoid/chrome:72.0 | selenoid/vnc:chrome_72.0 | 2.46 | 72.0.3626.121
| selenoid/chrome:73.0 | selenoid/vnc:chrome_73.0 | 73.0.3683.68 | 73.0.3683.75
|===

[NOTE]
====
. These images work with any modern Selenium client version.
. Images for older Chrome versions were not built because we have no Debian packages. If you have such packages - we could create more images.
====

=== Opera

.Opera Presto Images
|===
| Image | VNC Image | Selenium version | Opera version

| selenoid/opera:12.16 | selenoid/vnc:opera_12.16 | 2.37.0 | 12.16.1860 (dialogs and probably async JS don't work)
|===

[WARNING]
====
Due to bug in *Operadriver* to work with *Opera Blink* images you need to pass additional capability:
[source,javascript]
{"browserName": "opera", "operaOptions": {"binary": "/usr/bin/opera"}}

We do not consider these images really stable. Many of base operations like working with proxies may not work.
====

.Opera Blink Images
|===
| Image | VNC Image | Operadriver version | Opera version

| selenoid/opera:33.0 | selenoid/vnc:opera_33.0 | 0.2.2 | 33.0.1990.115
| selenoid/opera:34.0 | selenoid/vnc:opera_34.0 | 0.2.2 | 34.0.2036.50
| selenoid/opera:35.0 | selenoid/vnc:opera_35.0 | 0.2.2 | 35.0.2066.92
| selenoid/opera:36.0 | selenoid/vnc:opera_36.0 | 0.2.2 | 36.0.2130.65
| selenoid/opera:37.0 | selenoid/vnc:opera_37.0 | 0.2.2 | 37.0.2178.54
| selenoid/opera:38.0 | selenoid/vnc:opera_38.0 | 0.2.2 | 38.0.2220.41
| selenoid/opera:39.0 | selenoid/vnc:opera_39.0 | 0.2.2 | 39.0.2256.71
| selenoid/opera:40.0 | selenoid/vnc:opera_40.0 | 0.2.2 | 40.0.2308.90
| selenoid/opera:41.0 | selenoid/vnc:opera_41.0 | 2.27 | 41.0.2353.69
| selenoid/opera:42.0 | selenoid/vnc:opera_42.0 | 2.27 | 42.0.2393.94
| selenoid/opera:43.0 | selenoid/vnc:opera_43.0 | 2.27 | 43.0.2442.991
| selenoid/opera:44.0 | selenoid/vnc:opera_44.0 | 2.27 | 44.0.2510.857
| selenoid/opera:45.0 | selenoid/vnc:opera_45.0 | 2.27 | 45.0.2552.635
| selenoid/opera:46.0 | selenoid/vnc:opera_46.0 | 2.27 | 46.0.2597.26
| selenoid/opera:47.0 | selenoid/vnc:opera_47.0 | 2.29 | 47.0.2631.39
| selenoid/opera:48.0 | selenoid/vnc:opera_48.0 | 2.30 | 48.0.2685.35
| selenoid/opera:49.0 | selenoid/vnc:opera_49.0 | 2.32 | 49.0.2725.39
| selenoid/opera:50.0 | selenoid/vnc:opera_50.0 | 2.32 | 50.0.2762.45
| selenoid/opera:51.0 | selenoid/vnc:opera_51.0 | 2.33 | 51.0.2830.26
| selenoid/opera:52.0 | selenoid/vnc:opera_52.0 | 2.35 | 52.0.2871.37
| selenoid/opera:53.0 | selenoid/vnc:opera_53.0 | 2.36 | 53.0.2907.68
| selenoid/opera:54.0 | selenoid/vnc:opera_54.0 | 2.37 | 54.0.2952.46
| selenoid/opera:55.0 | selenoid/vnc:opera_55.0 | 2.37 | 55.0.2994.37
| selenoid/opera:56.0 | selenoid/vnc:opera_56.0 | 2.40 | 56.0.3051.31
| selenoid/opera:57.0 | selenoid/vnc:opera_57.0 | 2.41 | 57.0.3098.76
| selenoid/opera:58.0 | selenoid/vnc:opera_58.0 | 2.42 | 58.0.3135.79
| - | - | - | 59.0.x.x (no stable release exists)
| selenoid/opera:60.0 | selenoid/vnc:opera_60.0 | 2.45 | 60.0.3255.56
|===

[NOTE]
====
. These images work with any modern Selenium client version.
. Images for older Opera versions were not built because we have no Debian packages. If you have such packages - we could create more images.
====

=== Android

WARNING: Hardware server or virtual machine with nested virtualization support is required to run Android images.

.Android Images
|===
| Image | Android version | Appium version

| selenoid/android:4.4 | 4.4 | 1.8.1
| selenoid/android:5.1 | 5.1 | 1.8.1
| selenoid/android:6.0 | 6.0 | 1.8.1
| selenoid/android:7.0 | 7.0 | 1.8.1
| selenoid/android:7.1 | 7.1 | 1.8.1
| selenoid/android:8.0 | 8.0 | 1.8.1
| selenoid/android:8.1 | 8.1 | 1.8.1
|===

[NOTE]
====
. These images include VNC server and Android Quick Boot snapshot.
. Neither Chromedriver nor Chrome Mobile are installed. To test hybrid apps build your own image using provided automation script.
====


Polishing docs

Polishing docs

=== A Brief History of Spring's Data Integration Journey

Spring's journey on Data Integration started with https://projects.spring.io/spring-integration/[Spring Integration]. With its programming model, it provided a consistent developer experience to build applications that can embrace http://www.enterpriseintegrationpatterns.com/[Enterprise Integration Patterns] to connect with external systems such as, databases, message brokers, and among others.

Fast forward to the cloud-era, where microservices have become prominent in the enterprise setting. https://projects.spring.io/spring-boot/[Spring Boot] transformed the way how developers built Applications. With Spring's programming model and the runtime responsibilities handled by Spring Boot, it became seamless to develop stand-alone, production-grade Spring-based microservices.

To extend this to Data Integration workloads, Spring Integration and Spring Boot were put together into a new project. Spring Cloud Stream was born.

[%hardbreaks]
With Spring Cloud Stream, developers can:
* Build, test, iterate, and deploy data-centric applications in isolation.
* Apply modern microservices architecture patterns, including composition through messaging.
* Decouple application responsibilities with event-centric thinking. An event can represent something that has happened in time, to which the downstream consumer applications can react without knowing where it originated or the producer's identity.
* Port the business logic onto message brokers (such as RabbitMQ, Apache Kafka, Amazon Kinesis).
* Interoperate between channel-based and non-channel-based application binding scenarios to support stateless and stateful computations by using Project Reactor's Flux and Kafka Streams APIs.
* Rely on the framework's automatic content-type support for common use-cases. Extending to different data conversion types is possible.

=== Quick Start

You can try Spring Cloud Stream in less then 5 min even before you jump into any details by following this three-step guide.

We show you how to create a Spring Cloud Stream application that receives messages coming from the messaging middleware of your choice (more on this later) and logs received messages to the console.
We call it `LoggingConsumer`.
While not very practical, it provides a good introduction to some of the main concepts
and abstractions, making it easier to digest the rest of this user guide.

The three steps are as follows:

. <<spring-cloud-stream-preface-creating-sample-application>>
. <<spring-cloud-stream-preface-importing-project>>
. <<spring-cloud-stream-preface-adding-message-handler>>

[[spring-cloud-stream-preface-creating-sample-application]]
==== Creating a Sample Application by Using Spring Initializr
To get started, visit the https://start.spring.io[Spring Initializr]. From there, you can generate our `LoggingConsumer` application. To do so:

. In the *Dependencies* section, start typing `stream`.
When the "`Cloud Stream`" option should appears, select it.
. Start typing either 'kafka' or 'rabbit'.
. Select "`Kafka`" or "`RabbitMQ`".
+
Basically, you choose the messaging middleware to which your application binds.
We recommend using the one you have already installed or feel more comfortable with installing and running.
Also, as you can see from the Initilaizer screen, there are a few other options you can choose.
For example, you can choose Gradle as your build tool instead of Maven (the default).
. In the *Artifact* field, type 'logging-consumer'.
+
The value of the *Artifact* field becomes the application name.
If you chose RabbitMQ for the middleware, your Spring Initializr should now be as follows:

[%hardbreaks]
[%hardbreaks]
[%hardbreaks]
image::{github-raw}/docs/src/main/asciidoc/images/spring-initializr.png[align="center"]

[%hardbreaks]
[%hardbreaks]

. Click the *Generate Project* button.
+
Doing so downloads the zipped version of the generated project to your hard drive.
. Unzip the file into the folder you want to use as your project directory.

TIP: We encourage you to explore the many possibilities available in the Spring Initializr.
It lets you create many different kinds of Spring applications.

[[spring-cloud-stream-preface-importing-project]]
==== Importing the Project into Your IDE

Now you can import the project into your IDE.
Keep in mind that, depending on the IDE, you may need to follow a specific import procedure.
For example, depending on how the project was generated (Maven or Gradle), you may need to follow specific import procedure (for example, in Eclipse or STS, you need to use File -> Import -> Maven -> Existing Maven Project).

Once imported, the project must have no errors of any kind. Also, `src/main/java` should contain `com.example.loggingconsumer.LoggingConsumerApplication`.

Technically, at this point, you can run the application's main class.
It is already a valid Spring Boot application.
However, it does not do anything, so we want to add some code.

[[spring-cloud-stream-preface-adding-message-handler]]
==== Adding a Message Handler, Building, and Running

Modify the `com.example.loggingconsumer.LoggingConsumerApplication` class to look as follows:

[source, java]
----
@SpringBootApplication
public class LoggingConsumerApplication {

	public static void main(String[] args) {
		SpringApplication.run(LoggingConsumerApplication.class, args);
	}

	@Bean
	public Consumer<Person> log() {
	    return person -> {
	        System.out.println("Received: " + person);
	    };
	}

	public static class Person {
		private String name;
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public String toString() {
			return this.name;
		}
	}
}
----

As you can see from the preceding listing:

* We are using functional programming model (see <<Spring Cloud Function support>>) to define a single message handler as `Consumer`.
* We are relying on framework conventions to bind such handler to the input destination binding exposed by the binder.

Doing so also lets you see one of the core features of the framework: It tries to automatically convert incoming message payloads to type `Person`.

You now have a fully functional Spring Cloud Stream application that does listens for messages.
From here, for simplicity, we assume you selected RabbitMQ in <<spring-cloud-stream-preface-creating-sample-application,step one>>.
Assuming you have RabbitMQ installed and running, you can start the application by running its `main` method in your IDE.

You should see following output:

[source]
----
	--- [ main] c.s.b.r.p.RabbitExchangeQueueProvisioner : declaring queue for inbound: input.anonymous.CbMIwdkJSBO1ZoPDOtHtCg, bound to: input
	--- [ main] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: [localhost:5672]
	--- [ main] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#2a3a299:0/SimpleConnection@66c83fc8. . .
	. . .
	--- [ main] o.s.i.a.i.AmqpInboundChannelAdapter      : started inbound.input.anonymous.CbMIwdkJSBO1ZoPDOtHtCg
	. . .
	--- [ main] c.e.l.LoggingConsumerApplication         : Started LoggingConsumerApplication in 2.531 seconds (JVM running for 2.897)
----

Go to the RabbitMQ management console or any other RabbitMQ client and send a message to `input.anonymous.CbMIwdkJSBO1ZoPDOtHtCg`.
The `anonymous.CbMIwdkJSBO1ZoPDOtHtCg` part represents the group name and is generated, so it is bound to be different in your environment.
For something more predictable, you can use an explicit group name by setting `spring.cloud.stream.bindings.input.group=hello` (or whatever name you like).

The contents of the message should be a JSON representation of the `Person` class, as follows:

	{"name":"Sam Spade"}

Then, in your console, you should see:

`Received: Sam Spade`

You can also build and package your application into a boot jar (by using `./mvnw clean install`) and run the built JAR by using the `java -jar` command.

Now you have a working (albeit very basic) Spring Cloud Stream application.

== What's New in 3.0?

[[spring-cloud-stream-preface-new-features]]
=== New Features and Enhancements

- *Routing Function* - see <<Routing with functions>> for more details.
- *Multiple bindings with functions* (multiple message handlers) - see <<Multiple functions in a single application>> for more details.
- *Functions with multiple inputs/outputs* (single function that can subscribe or target multiple destinations) - see <<Functions with multiple input and output arguments>> for more details.
- *Native support for reactive programming* - since v3.0.0 we no longer distribute spring-cloud-stream-reactive modules and instead
relying on native reactive support provided by spring cloud function. For backward
compatibility you can still bring `spring-cloud-stream-reactive` from previous versions.

[[spring-cloud-stream-preface-notable-deprecations]]
=== Notable Deprecations

- _Reactive module_ (`spring-cloud-stream-reactive`) is discontinued and no longer distributed in favor of native support via spring-cloud-function.
For backward
compatibility you can still bring `spring-cloud-stream-reactive` from previous versions.
- _Test support binder_ `spring-cloud-stream-test-support` with MessageCollector in favor of a new test binder. See <<Testing>> for more details.
- _@StreamMessageConverter_ - deprecated as it is no longer required.
- The `original-content-type` header references have been removed after it's been deprecated in v2.0.

=== A Brief History of Spring's Data Integration Journey

Spring's journey on Data Integration started with https://projects.spring.io/spring-integration/[Spring Integration]. With its programming model, it provided a consistent developer experience to build applications that can embrace http://www.enterpriseintegrationpatterns.com/[Enterprise Integration Patterns] to connect with external systems such as, databases, message brokers, and among others.

Fast forward to the cloud-era, where microservices have become prominent in the enterprise setting. https://projects.spring.io/spring-boot/[Spring Boot] transformed the way how developers built Applications. With Spring's programming model and the runtime responsibilities handled by Spring Boot, it became seamless to develop stand-alone, production-grade Spring-based microservices.

To extend this to Data Integration workloads, Spring Integration and Spring Boot were put together into a new project. Spring Cloud Stream was born.

[%hardbreaks]
With Spring Cloud Stream, developers can:
* Build, test, iterate, and deploy data-centric applications in isolation.
* Apply modern microservices architecture patterns, including composition through messaging.
* Decouple application responsibilities with event-centric thinking. An event can represent something that has happened in time, to which the downstream consumer applications can react without knowing where it originated or the producer's identity.
* Port the business logic onto message brokers (such as RabbitMQ, Apache Kafka, Amazon Kinesis).
* Interoperate between channel-based and non-channel-based application binding scenarios to support stateless and stateful computations by using Project Reactor's Flux and Kafka Streams APIs.
* Rely on the framework's automatic content-type support for common use-cases. Extending to different data conversion types is possible.

=== Quick Start

You can try Spring Cloud Stream in less then 5 min even before you jump into any details by following this three-step guide.

We show you how to create a Spring Cloud Stream application that receives messages coming from the messaging middleware of your choice (more on this later) and logs received messages to the console.
We call it `LoggingConsumer`.
While not very practical, it provides a good introduction to some of the main concepts
and abstractions, making it easier to digest the rest of this user guide.

The three steps are as follows:

. <<spring-cloud-stream-preface-creating-sample-application>>
. <<spring-cloud-stream-preface-importing-project>>
. <<spring-cloud-stream-preface-adding-message-handler>>

[[spring-cloud-stream-preface-creating-sample-application]]
==== Creating a Sample Application by Using Spring Initializr
To get started, visit the https://start.spring.io[Spring Initializr]. From there, you can generate our `LoggingConsumer` application. To do so:

. In the *Dependencies* section, start typing `stream`.
When the "`Cloud Stream`" option should appears, select it.
. Start typing either 'kafka' or 'rabbit'.
. Select "`Kafka`" or "`RabbitMQ`".
+
Basically, you choose the messaging middleware to which your application binds.
We recommend using the one you have already installed or feel more comfortable with installing and running.
Also, as you can see from the Initilaizer screen, there are a few other options you can choose.
For example, you can choose Gradle as your build tool instead of Maven (the default).
. In the *Artifact* field, type 'logging-consumer'.
+
The value of the *Artifact* field becomes the application name.
If you chose RabbitMQ for the middleware, your Spring Initializr should now be as follows:

[%hardbreaks]
[%hardbreaks]
[%hardbreaks]
image::{github-raw}/docs/src/main/asciidoc/images/spring-initializr.png[align="center"]

[%hardbreaks]
[%hardbreaks]

. Click the *Generate Project* button.
+
Doing so downloads the zipped version of the generated project to your hard drive.
. Unzip the file into the folder you want to use as your project directory.

TIP: We encourage you to explore the many possibilities available in the Spring Initializr.
It lets you create many different kinds of Spring applications.

[[spring-cloud-stream-preface-importing-project]]
==== Importing the Project into Your IDE

Now you can import the project into your IDE.
Keep in mind that, depending on the IDE, you may need to follow a specific import procedure.
For example, depending on how the project was generated (Maven or Gradle), you may need to follow specific import procedure (for example, in Eclipse or STS, you need to use File -> Import -> Maven -> Existing Maven Project).

Once imported, the project must have no errors of any kind. Also, `src/main/java` should contain `com.example.loggingconsumer.LoggingConsumerApplication`.

Technically, at this point, you can run the application's main class.
It is already a valid Spring Boot application.
However, it does not do anything, so we want to add some code.

[[spring-cloud-stream-preface-adding-message-handler]]
==== Adding a Message Handler, Building, and Running

Modify the `com.example.loggingconsumer.LoggingConsumerApplication` class to look as follows:

[source, java]
----
@SpringBootApplication
public class LoggingConsumerApplication {

	public static void main(String[] args) {
		SpringApplication.run(LoggingConsumerApplication.class, args);
	}

	@Bean
	public Consumer<Person> log() {
	    return person -> {
	        System.out.println("Received: " + person);
	    };
	}

	public static class Person {
		private String name;
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public String toString() {
			return this.name;
		}
	}
}
----

As you can see from the preceding listing:

* We are using functional programming model (see <<Spring Cloud Function support>>) to define a single message handler as `Consumer`.
* We are relying on framework conventions to bind such handler to the input destination binding exposed by the binder.

Doing so also lets you see one of the core features of the framework: It tries to automatically convert incoming message payloads to type `Person`.

You now have a fully functional Spring Cloud Stream application that does listens for messages.
From here, for simplicity, we assume you selected RabbitMQ in <<spring-cloud-stream-preface-creating-sample-application,step one>>.
Assuming you have RabbitMQ installed and running, you can start the application by running its `main` method in your IDE.

You should see following output:

[source]
----
	--- [ main] c.s.b.r.p.RabbitExchangeQueueProvisioner : declaring queue for inbound: input.anonymous.CbMIwdkJSBO1ZoPDOtHtCg, bound to: input
	--- [ main] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: [localhost:5672]
	--- [ main] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#2a3a299:0/SimpleConnection@66c83fc8. . .
	. . .
	--- [ main] o.s.i.a.i.AmqpInboundChannelAdapter      : started inbound.input.anonymous.CbMIwdkJSBO1ZoPDOtHtCg
	. . .
	--- [ main] c.e.l.LoggingConsumerApplication         : Started LoggingConsumerApplication in 2.531 seconds (JVM running for 2.897)
----

Go to the RabbitMQ management console or any other RabbitMQ client and send a message to `input.anonymous.CbMIwdkJSBO1ZoPDOtHtCg`.
The `anonymous.CbMIwdkJSBO1ZoPDOtHtCg` part represents the group name and is generated, so it is bound to be different in your environment.
For something more predictable, you can use an explicit group name by setting `spring.cloud.stream.bindings.input.group=hello` (or whatever name you like).

The contents of the message should be a JSON representation of the `Person` class, as follows:

	{"name":"Sam Spade"}

Then, in your console, you should see:

`Received: Sam Spade`

You can also build and package your application into a boot jar (by using `./mvnw clean install`) and run the built JAR by using the `java -jar` command.

Now you have a working (albeit very basic) Spring Cloud Stream application.

== What's New in 3.0?

[[spring-cloud-stream-preface-new-features]]
=== New Features and Components

- *Routing Function* - see <<Routing with functions>> for more details.
- *Multiple bindings with functions* (multiple message handlers) - see <<Multiple functions in a single application>> for more details.
- *Functions with multiple inputs/outputs* (single function that can subscribe or target multiple destinations) - see <<Functions with multiple input and output arguments>> for more details.
- *Native support for reactive programming* - since v3.0.0 we no longer distribute spring-cloud-stream-reactive modules and instead
relying on native reactive support provided by spring cloud function. For backward
compatibility you can still bring `spring-cloud-stream-reactive` from previous versions.

[[spring-cloud-stream-preface-notable-enhancements]]
=== Notable Enhancements
TBD

[[spring-cloud-stream-preface-notable-deprecations]]
=== Notable Deprecations

- Reactive module (`spring-cloud-stream-reactive`) is discontinued and no longer distributed in favor of native support via spring-cloud-function.
For backward
compatibility you can still bring `spring-cloud-stream-reactive` from previous versions.
- We've deprecated `spring-cloud-stream-test-support` with MessageCollector in favor of a new test binder. See <<Testing>> for more details.
- @StreamMessageConverter - deprecated as it is no longer required.
- The reliance on the `original-content-type` has been removed after it's been deprecated in v2.0.


Corrected moving the Camera topic.

Corrected moving the Camera topic.

Corrected moving the Camera topic not to use a deprecated method.

= Remote-Controlling the Camera
:author:
:revnumber:
:revdate: 2016/03/17 20:48
:keywords: camera, documentation, cinematics
:relfileprefix: ../../
:imagesdir: ../..
ifdef::env-github,env-browser[:outfilesuffix: .adoc]

== Positioning the Camera

You can steer the camera using <<jme3/advanced/cinematics#,Cinematics>>:

.  Create a Cinematic.
.  Create a CameraNode and bind the camera object to the Cinematic. Note that we also give the camera node a name in this step.
+
[source,java]
----
CameraNode camNode = cinematic.bindCamera("topView", cam);
----

.  Position the camera node in its start location.
.  Use activateCamera() to give the control of the camera to this node. You now see the scene from this camera's point of view. For example to see through the camera node named “topView, 6 seconds after the start of the cinematic, you'd write
+
[source,java]
----
cinematic.activateCamera(6, "topView");
----

=== Code Sample

[source,java]
----

flyCam.setEnabled(false);
Cinematic cinematic = new Cinematic(rootNode, 20);

CameraNode camNodeTop = cinematic.bindCamera("topView", cam);
camNodeTop.setControlDir(ControlDirection.SpatialToCamera);
camNodeTop.getControl(0).setEnabled(false);

CameraNode camNodeSide = cinematic.bindCamera("sideView", cam);
camNodeSide.setControlDir(ControlDirection.CameraToSpatial);
camNodeSide.getControl(0).setEnabled(false);

----

== Moving the Camera

If desired, attach the camNode to a MotionEvent to let it travel along waypoints. This is demonstrated in the link:https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-examples/src/main/java/jme3test/animation/TestCameraMotionPath.java[TestCameraMotionPath.java] example.

= Remote-Controlling the Camera
:author:
:revnumber:
:revdate: 2016/03/17 20:48
:keywords: camera, documentation, cinematics
:relfileprefix: ../../
:imagesdir: ../..
ifdef::env-github,env-browser[:outfilesuffix: .adoc]

== Positioning the Camera

You can steer the camera using <<jme3/advanced/cinematics#,Cinematics>>:

.  Create a Cinematic.
.  Create a CameraNode and bind the camera object to the Cinematic. Note that we also give the camera node a name in this step.
+
[source,java]
----
CameraNode camNode = cinematic.bindCamera("topView", cam);
----

.  Position the camera node in its start location.
.  Use activateCamera() to give the control of the camera to this node. You now see the scene from this camera's point of view. For example to see through the camera node named “topView, 6 seconds after the start of the cinematic, you'd write
+
[source,java]
----
cinematic.activateCamera(6, "topView");
----

=== Code Sample

[source,java]
----

flyCam.setEnabled(false);
Cinematic cinematic = new Cinematic(rootNode, 20);

CameraNode camNodeTop = cinematic.bindCamera("topView", cam);
camNodeTop.setControlDir(ControlDirection.SpatialToCamera);
camNodeTop.getControl(0).setEnabled(false);

CameraNode camNodeSide = cinematic.bindCamera("sideView", cam);
camNodeSide.setControlDir(ControlDirection.CameraToSpatial);
camNodeSide.getControl(0).setEnabled(false);

----

== Moving the Camera

If desired, attach the camNode to a MotionTrack to let it travel along waypoints. This is demonstrated in the link:https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-examples/src/main/java/jme3test/animation/TestCameraMotionPath.java[TestCameraMotionPath.java] example.


Update deployment-overview.adoc

Update deployment-overview.adoc

=== Overview

Kill Bill (whether used as a Billing System and/or Payment System) is fundamentally a backend system, so the following considerations should apply:

* Don't make the system visible to the outside world, instead you should have a front end system, or a reverse proxy in front of it; if you need to handle gateway notifications (e.g. IPN/Internal Payment Notification), we developed an https://github.com/killbill/killbill-notifications-proxy[app] for that specific purpose
* Always deploy at least 2 instances (for reliability purpose) in front of a load balancer
* Setup your database with a master and a slave instances, and configure it to take regular snapshots
* Aggregate your logs (for example using http://logstash.net/[LogStash], https://www.graylog.org/[GrayLog2], ...) and take a look at our https://github.com/killbill/killbill/wiki/Logback-recipes[Logback recipes]
* Look at the all the existing Kill Bill JMX metrics (using VisualVM, jconsole, ...) and set some alerts (for instance by using the following https://github.com/killbill/nagios-jmx-plugin[script])
* Configure your system properly, especially when it comes to the https://github.com/killbill/killbill/wiki/Kill-Bill-Bus-and-Notification-Queue-Configuration[settings of the bus and notification queue].

When deploying Kill Bill, the following pieces will need to be deployed in addition to your OS, VM or container (Docker, ...):

* A web container (Jetty, Tomcat, ...)
* The killbill war
* The plugins
* The configuration files and system properties
* The database along with the various schemas: the Kill Bill schema is available from our http://killbill.io/downloads[download page] and each stateful plugin will/should expose such a schema (for example, here is the https://github.com/killbill/killbill-adyen-plugin/blob/master/src/main/resources/ddl.sql[schema] for the Adyen plugin)

In order to ease the deployment, we created https://github.com/killbill/killbill-cloud/tree/master/kpm[KPM], the Kill Bill Package Manager. KPM can fetch existing (signed) artifacts for the main killbill war and for each of the plugins, and deploy them at the right place. KPM can also deploy Tomcat.

In addition to KPM, we also provide https://registry.hub.docker.com/u/killbill/killbill[Docker images]. Those images internally rely on KPM for fetching the various artifacts and install them at the right location on the image. The docker `run` command allows to override existing Kill Bill system properties.

Note that one can decide to only use KPM for fetching and installing artifacts, and bypass docker to deploy on VMs or bare metal. Or alternatively one can use other tools, manual steps to fetch those artifacts from http://search.maven.org/[Maven Central], or by using our convenient http://killbill.io/downloads[download page].

=== Overview

Kill Bill (whether used as a Billing System or Payment System) is fundamentally a backend system and so the following consideration should apply:

* Don't make the system visible to the outside world, you should have a front end system, or a reverse proxy in front of it; if you need to handle some IPN (Internal Payment Notification), we developed an https://github.com/killbill/killbill-notifications-proxy[app] for that specific purpose.
* Always deploy at least 2 instances (for reliability purpose) in front of a load balancer.
* Setup your database with a master and a slave instances, and configure it to take regular snapshots
* Aggregate your logs (for example using http://logstash.net/[LogStash], https://www.graylog.org/[GrayLog2], ...) and take a look at our https://github.com/killbill/killbill/wiki/Logback-recipes[logback recipes]
* Look at the all the existing Kill Bill JMX metrics (using VisualVM, jconsole, ...) and set some alerts (for instance by using the following https://github.com/killbill/nagios-jmx-plugin[script])
* Configure your system properly, especially when it comes to the https://github.com/killbill/killbill/wiki/Kill-Bill-Bus-and-Notification-Queue-Configuration[settings of the bus and notification queue].

When deploying Kill Bill, the following pieces will need to be deployed in addition to your OS, VM or container (Docker, ...):

* A web container (Jetty, Tomcat, ...)
* The killbill war
* The plugins
* The configuration files and system properties
* The database along with the various schemas: the Kill Bill schema is available from our http://killbill.io/downloads[download page] and each stateful plugin will/should expose such a schema (for example, here is the https://github.com/killbill/killbill-adyen-plugin/blob/master/src/main/resources/ddl.sql[schema] for the Adyen plugin)

In order to ease the deployment, we created https://github.com/killbill/killbill-cloud/tree/master/kpm[KPM], the Kill Bill Package Manager. KPM can fetch existing (signed) artifacts for the main killbill war and for each of the plugins, and deploy them at the right place. KPM can also deploy Tomcat.

In addition to KPM, we also provide https://registry.hub.docker.com/u/killbill/killbill[Docker images]. Those images internally rely on KPM for fetching the various artifacts and install them at the right location on the image. The docker `run` command allows to override existing Kill Bill system properties.

Note that one can decide to only use KPM for fetching and installing artifacts, and bypass docker to deploy on VMs or bare metal. Or alternatively one can use other tools, manual steps to fetch those artifacts from http://search.maven.org/[Maven Central], or by using our convenient http://killbill.io/downloads[download page].

The following sections below will give manual instructions on how to deploy on various Java containers.


Update 2015-12-02-Inverser-le-controle-avec-StructueMap.adoc

= Inverser le contrôle avec StructueMap !
:hp-image: introduction-a-angular2.png
:published_at: 2015-12-02
:hp-tags: C#, IoC, StructureMap

L'inversion de contrôle (aka *IoC*) est un pattron d'architecture permettant de casser les adhérenses entre les dépendances. Ce découplage permet au dépendances de haut niveau de ne plus reposer directement sur celle de bas niveau et de passer par une couche d'abstration. L'objectif est d'améliorer la modularité des applications afin de faciliter les étapes de maintenance et d'évolution.

== Pourquoi ce pattern ?

Avant de montrer la mise en place de l'inversion de contrôle il est préférable d'expliquer comment on en est arrivé là !

Le problème de départ est assez simple en réalité. Si on a une application développée en couche et que l'ont fait reposer chaqu'une d'elle sur une autre de plus bas niveau, que va-t-il se passer si par la suite on veut changer, modifier, supprimer la couche la plus basse ? Le problème est là, il va falloir modifier la couche de plus haut niveau pour l'adapter en conséquence. Il est d'ailleurs fort probable que cela déclencher des modification en cascade sur toutes les couches haut niveau.

Avec l'inversion de contrôle on va plutôt faire reposer chaque couche sur une abstraction afin de passer de ça :

[Schéma]

A ça :

[Schéma]

La différence est suptile mais importante. Dans le second shémas, changer les couches concrêtes de mon application n'entraine pas de problèmes de structure, tout compilera. Mieux encore, le développement de l'application peut être abstrait et n'avoir aucunes connaissances du fonctionnement bas niveau. Plus fort, je peux définir des abstractions utilisés par les couches de bas niveau qui sont implémentées en haut niveau. Un autre intéressant, les test unitaires deviennent beaucoup plus simple et pertinent car les dépendances ne rentre plus en compte dans l'étape de validation.

Au final en utilisant l'inversion de contrôle, votre application va devenir plus modulaire, souple, maintenable, évolutive et testable.

NOTE: L'inversion de contrôle permet aussi de respecter un des principes majeurs de https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)[SOLID] : l'inversion des dépendances. Ce principe définit qu'il faut toujours dépendre des abstractions et non des implémentations. Les principes SOLID sont considérer par beaucoup comme les _"Best Pratices"_ de la POO. Ils permettent d'avoir un développement éfficace et fiable.

Parmis les manières les plus courrante de mettre en place l'IoC, il existe :

* Le pattern https://en.wikipedia.org/wiki/Factory_(object-oriented_programming)[Factory] qui permet de délégué la création d'instance. Une approche rudimentaire mais qui a le mérite d'être simple dans sa mise en place.
* Le pattern https://en.wikipedia.org/wiki/Service_locator_pattern[Service Locator] qui via l'utilisation d'un registrer permet de retourver des implémentations correspondant à des contrats.
* Le pattern https://en.wikipedia.org/wiki/Dependency_injection[Dependancy injection] qui permet de passer une dépendance à un object dépendant.

La suite de l'article va se concentrer sur le troisième pattern, Depenceny Injection. Factory est un pattern trop simplice pour permettre de résoudre les dépendances à lui seul et Service Locator est devenu au fil du temps un anti-pattern (notamment parce qu'il crée de l'ahérence alors que c'est l'inverse que l'ont veut).

== StructureMap en action : les bases !

Il existe de nombreuse bibliothèque dédiée à l'IoC. Sincérement il n'y a pas de mauvais choix même si j'ai tendance à déconseiller celle de Microsoft (https://github.com/unitycontainer/unity[Unity]) qui est à la traine par rapport à la concurrence. Pour ma part j'ai longtemps utilisé Ninject pour son côté _"Simple & Straightforward"_, très éfficace en développement bien qu'un peu lent à l'execution il faut l'admettre. Dernièrement j'ai décidé de changer et je me suis mis sur StructureMap, un container historique (le premier en C#) qui embarque d'innombrables capacités pour un niveau de performance très correct. C'est un bon compromis entre capacité et puissance et depuis la version 3 l'équipe à fait un gros travail pour se rapporcher de l'approche de Ninject. De plus il est en développement actif depuis presque 10 ans, il dispose d'une documentation riche et complète et est le premier à disposer d'un support pour DNX avec la version 4. C'est cette version que l'on va utilisé aujourd'hui !

Pour installer le package StructureMap il suffit d'ajouter le package Nuget via la commande :

----
Install-Package StructureMap
----

La première classe qui va nous intéresser est Container. C'est l'élément centrale de la librairie. Elle va nous permettre de définir les dépendances à résoudres, leur façon d'être résolut et leur récupération.
Pour définir une dépendance il nous faut un contract. Pour les besoins de l'article je vais en définir un :

[source,csharp]
----
public interface ILogger
{
	void Information(string message);
}
----

L'interface correspond à un logger qui va tracer un message d'information. Son implémentation est la suivante :

[source,csharp]
----
public class ConsoleLogger : ILogger
{
	public void Information(string message)
	{
		Console.WriteLine(message);
	}
}
----

Pour résoudre cette dépendance via le container il faut la définir. La classe possède un constructeur acceptant en paramètre une action qui expose un object de configuration. Cette object permet de définir les liaisons entre contrat et implémentation à l'aide des méthodes For<T> et Use<T> :

[source,csharp]
----
var container = new Container(configuration =>
{
	configuration.For<ILogger>().Use<ConsoleLogger>();
});
----

Ces deux méthodes disposent d'une surcharge qui permet de passer en paramètre des object Type. Le résultat est le même mais syntaxiquement c'est plus lourd à lire. Il lié le contrat à une implémentation il existe aussi la méthode Add<T>, ce code fonctionne donc aussi :

[source,csharp]
----
configuration.For<ILogger>().Add<Logger>();
----

NOTE: Il existe une différenciation très nette entre les deux méthode. Add<T> permet d'ajouter une implémentation alors que Use<T> définit l'implementation comme strictement à utiliser. En conséquence lors que vous utilisez Use<T> c'est toujours la dernière liaison à une implémentation qui l'emporte.

Parfois il est nécessaire de définir comment le Container va créer l'instance, dans se cas il est possible d'utilise une surcharge de la méthode Use qui accept en paramètre une Function qui a en paramètre le context du containeret qui retourne une instance correspondant au contrat :

[source,csharp]
----
configuration.For<ILogger>().Use("Logger instance creation", (x) =>
{
	return new ConsoleLogger();
});
----

La dépendance est à présent définit. Le code ne contient pas de logique de création des implémentations c'est donc l'instance de la class Container qui va donc les construire pour nous. Il est possible de les récupérer en appelant la méthode GetInstance<T>() :

[source,csharp]
----
var logger = container.GetInstance<ILogger>();
----

WARNING: Attention, la méthode GetInstance<T> peut très bien accepter des types implémentés sans que cela ne pose de problèmes. Cependant en faisant ça il y a une rupture avec le principe SOLID d'inversion de dépendance. N'oubliez pas : Il faut dépendre des abstractions, pas des implémentations.

Si une dépendance n'est pas définit et que l'ont souhaite la résoudre, l'instance de Container léve une Exception de configuration. Il peut y avoir des cas où l'ont ne sait pas si une liaison est établie entre un contrat et une implémentation. Dans ce cas il vaut mieux utiliser la méthode TryGetInstance<T> et tester si l'instance est retournée est null, c'est plus optimisé que de traiter le cas avec un bloc try catch :

[source,csharp]
----
//Aucune implémentation de IDisposable n'est configurés actuellement
var disposable = container.GetInstance<IDisposable>();
Console.WriteLine(disposable == null);//print True
----

La méthode Add<T> permettant de définir plusieurs implémentation d'un même contrat, il existe une méthode GetAllInstances<T>() qui renvoie l'ensemble des implémentations correspondante. Si on rajoute une nouvelle implémentation de ILogger :

[source,csharp]
----
public class FileLogger : ILogger
{
	public void Information(string message)
	{
		File.WriteAllText("log.txt", message);
	}
}
----

Et qu'on l'ajoute les deux implémentations dans la configuration :

[source,csharp]
----
configuration.For<ILogger>().Add<ConsoleLogger>();
configuration.For<ILogger>().Add<FileLogger>();
----

L'appel à la méthode renverra une instance de IEnumerable<ILogger> :

[source,csharp]
----
var loggers = container.GetAllInstances<ILogger>();
Console.WriteLine(loggers.Count() == 2);//print True
----

A ce stade vous avez les bases pour résoudre des dépendances. Cependant l'utilisation actuelle du Container correspond à peu de chose prêt au pattern ServiceLocator. On aurait aussi pu faire une Factory pour gérer la création d'instance. Il est donc temps de s'intéresser à l'injection des dépendances.

== Injection

Pour injecter les dépendances il nous faut une classse plus haut niveau qui repose sur le contrat établit dans ILogger. Pour continuer avec un cas simple prenons l'exemple d'un controlleur qui, lors de l'éxecution d'une action, trace une information via le logger. Si l'ont veut casser l'adhérence il faut passer la dépendance implémentant ILogger via le constructeur :

[source,csharp]
----
public class Controller
{
	private ILogger _logger;

	public Controller(ILogger logger)
	{
		_logger = logger;
	}

	public void Action()
	{
		this._logger.Information("Hello from action !");
	}
}
----

Après avoir configurer le Container pour lié une des deux implémentations de ILogger, il est possible de récupérer une instance de la classe Controller via la méthode GetInstance<T>() :

[source,csharp]
----
var container = new Container(configuration =>
{
	configuration.For<ILogger>().Use<ConsoleLogger>();
});

var controller = container.GetInstance<Controller>();
controller.Action();// print "Hello from action !"
----

J'en vois déjà qui vont me répondre que j'ai mis un *Warning* plus haut concernant l'appel de GetInstance<T> sans utiliser une interface ! En réalité dans ce cas les choses sont différentes car Controller n'est pas une dépandance mais un dépendent. La résolution est donc conforme au principe d'inversion de dépendance. Cette manière de procéder est une des plus couramment utilisé dans le monde .Net (on l'a retrouve sous la forme de DependencyResolver dans les frameworks Microsoft).

StructureMap offre aussi la possibilité d'injecter les dépendances par inspection des propriétés plutôt que par le constructeur. C'est une alternative intéressante et facile à mettre en oeuvre. Déjà cela permet de réduire le code de la classe Controller à :

[source,csharp]
----
public class Controller
{
	public ILogger Logger { get; set; }

	public void Action()
	{
		this.Logger.Information("Hello from action !");
	}
}
----

Par défaut StructureMap ne résoudera pas la dépendance en l'état. Dans la configuration il faut, lors de la liaison entre un contrat et son implémentation, utiliser la méthode Setter pour spécifier quelle type de dépendance sous jacente va être injecter :

[source,csharp]
----
configuration.For<IController>().Use<Controller>().Setter<ILogger>().Is<ConsoleLogger>();
----

La méthode Setter<T> dispose d'une surcharge qui permet d'injecter à condition que le nom de la propriété match avec celui passé en argument :

[source,csharp]
----
configuration.For<IController>().Use<Controller>().Setter<ILogger>("Logger").Is<ConsoleLogger>();
----

NOTE: Cette surcharge peut s'avérer pratique, mais attention au renommage, on a vite fait d'oublier d'aller modifier la configuration du Container !

Une fois mise en place il suffit de demander à récupérer une instance d'une dépendance pour que celle ci soit automatiquement injecter avec celle sous-jacente :

[source,csharp]
----
//Avec Setter<T> il devient obligatoire de demander une instance correspondant à un contrat.
//IController doit donc être définit dans votre code pour que cela fonctionne
var controller = container.GetInstance<IController>();
controller.Action();// print "Hello from action !"
----

Cette première façon de faire est certes éfficace mais imaginez faire cela pour toutes les dépendances... ce n'est pas très élégant. En plus il devient compliquer de résoudre les dépendances sur des instances dépendant qu'on voudrait résoudre avec le container. Il existe donc une méthode plus éfficace pour mettre en place l'injection par propriété : Définir des conventions dans le registre des Policies du Container. Pour cela les développeurs de StructureMap on tout prévu, il existe une méthode SetAllProperties qui attent en paramètre une action exposant une instance de la classe SetterConvention :

[source,csharp]
----
configuration.Policies.SetAllProperties(convention => {

});
----

Plusieurs possibilés sont offertes pour définir une convention. La plus simple est celle qui définit une éxigence de type stricte :

[source,csharp]
----
convention.OfType<ILogger>();
----

Une convention par namespace peut être établit, elle permet de déclencher l'injection de la dépendance uniquement sur les Type appartenant à ce namespace. Deux possibilité de déclaration,  Soit par nom :

[source,csharp]
----
convention.WithAnyTypeFromNamespace("MyNamespace");
----

Soit en utilisant le namespace d'un type en particulier :

[source,csharp]
----
convention.WithAnyTypeFromNamespaceContainingType<ILogger>();
----

Ces trois exemples de convention permettent de traiter pas mal de cas. La classe SetterConvention offre plusieurs autres possibilités pour customiser sa logique d'injection. Le mieux c'est de les essayer pour voir celle qui conviennent aux besoins.

Sans s'en rendre compte, en utilisant les conventions, une nouvelle capacités du Container a été dévérouiller, le BuildUp ! Il est maintenant possible de se dispenser d'utiliser la méthode GetInstance<T>() pour obtenir une instance d'un dépendant avec ses dépendances injectées :

[source,csharp]
----
var controller = new Controller();
container.BuildUp(controller);
----

L'intérêt premier est qu'avec cette technique on va pouvoir injecter les dépendances d'une instance dont la création est faite par un tier. Mine de rien cela ouvre pas mal de possibilité et permet de s'interfacer avec d'autre Framework ou Api qui possède leur propre mécanique de création d'instance (On peut cité par exemple Asp.Net MVC avec les ControllerFactory). C'est aussi une façon de faire qui est plus proche de la théorie de l'inversion de contrôle car dans ce cas précis il n'y a plus aucun appel direct de résolution d'un dépendant, le Container se préoccupe de se qu'il sait faire de mieux, résoudres les dépendances !

Au niveau de la mécanique d'injection des dépendances nous avons vu les fondamentaux. Il faut savoir que la méthode priviliégié par la plus part des développeurs est celle injectant les dépendances par constructeur. Elle demande bien moins d'effort et évite d'avoir des propriétés publique manipulable par le premier venu. Cependant il existe de nombreux cas ou l'insepection par propriété est indispensanble (par exemple pour injecter des dépendances dans des attributs), donc il ne faut pas non plus complétement écarter cette possibilité. Personnellement j'utilise beaucoup l'injection par propriété car ça permet d'avoir une grande compacité de code en plus déviter de manipuler l'affectation des références à la main (je trouve que c'est plus clean sur ce point). Cependant quand je le fait je suis beaucoup plus stricte sur les règles de manipulation de ses instances. Il faut clairement que la responsabilité de leur manipulation soit faite uniquement par le dépendant, sinon c'est la porte ouvert à de nombreux problème.

== Lifecycle

Depuis le début de cette article la création des instances des dépendances a été complétement délégué à la classe Container. La grand question à présent est : Comment puis gérer le cycle de vie des instances au sein de mon container ?

StructureMap, comme tout les container IoC, met à disposition un ensemble de méthode et classe pour gérer cela. Par défaut lorsque l'ont ajouter une liaison entre un contrat et une implémentation, le container va définir leur cycle de vie à l'état Transient. Cela veut dire qu'à chaque fois qu'il résoudra une dépendance il créra une instance. Il est tout façon possible de mettre explicitement la dépendance dans le mode Transient :

[source,csharp]
----
configuration.For<ILogger>().Use<ConsoleLogger>().Transient();
----

Logiquement si l'on demande deux fois la dépendance, la référence est différente :

[source,csharp]
----
var logger = container.GetInstance<ILogger>();
var logger2 = container.GetInstance<ILogger>();
Console.WriteLine(logger == logger2);//print False
----

Un cas régulier dans le cycle de vie des objets c'est l'instance unique. Pour le faire on utilise la méthode Singleton :

[source,csharp]
----
configuration.For<ILogger>().Use<ConsoleLogger>().Singleton();
----

En conséquence le test de référence que l'on a fait précedement renvoie True :

[source,csharp]
----
var logger = container.GetInstance<ILogger>();
var logger2 = container.GetInstance<ILogger>();
Console.WriteLine(logger == logger2);//print True
----

Ces deux cas sont parmis les principaux que l'ont trouve dans le développement mais il en existe d'autre qui permettent de gérer la vie de l'instance sur un Thread ou encore dans un container encapsuler dans un scope temporaire (ContainerScoped, utiliser pour gérer notamment le cas de DNX).

Il existe une autre façon de déclarer un cycle de vie avec StructureMap. Dans la bibliothèque une interface ILifecycle permet d'implémenter facilement sont propre cycle de vie. Il est ensuite possible de l'utiliser dans la configuration avec la méthode LifecycleIs<T> (ou T implémente ILifecycle).

WARNING: Les cycles de vies personnalisés peuvent être utilsé dans bien des cas. Il existe des bibliothèques qui fournissent des cycles spécialisés, comme pour le Web par exemple. Avec la version 4 de StructureMap l'équipe de développement encouragement fortement a ne plus utilisé se mode fonctionnement mais à préférer l'utilisation du NestedContainer à la place. Il permette de repondre à 95% des cas de figures sans avoir besoin de librairie annexe ni de code supplémentaire.

Comme indiquer dans la note au dessus les NestedContainer permette aussi de gérer le cycle de vie des instances. En les utilisant on se simplifie clairement la vie car à partir du moment ou le NestedContainer est récupérer tout les instances qui sont obtenues lui sont propre. Il suffit ensuite d'appeller la méthode Dispose pour le libérer. C'est une méthode très fléxible puisqu'elle suffit d'établir un scope pour que cela fonctionne. En plus dans ce mode toutes les dépendances qui implemente IDisposable sont Dispose lors de l'appel à la méthode sur le NestedContainer. Du coup les trois états de StructureMap suffisent à gérer tous les cas, y compris ceux liée au context Web. Pour illustrer cela un Scope simple peut être imaginé : L'éxecution d'une ligne de code. Dans ce cas voici les étapes qui va falloir faire pour le mettre en place :

* Le NestedContainer est crée et démarrer le Scope
* Le code voulut est executé
* Le NestedContainer libère les dépendances.

Ces trois étapes peuvent bien évidement être rejouer à l'infinit. Pour illustrer le bon fonctionnement du Dispose, un implémentation de ILogger disposable va être utilisé :

[source,csharp]
----
public class DisposableConsoleLogger : ConsoleLogger, IDisposable
{
	public void Dispose()
	{
		Console.WriteLine("Hello form Dispose !");
	}
}
----

Pour la configuration rien ne change à par le Use<T> qui utilise DisposableConsoleLogger  :

[source,csharp]
----
var container = new Container(configuration =>
{
	configuration.For<ILogger>().Use<DisposableConsoleLogger>();
});
----

Le code des trois étapes est le suivant :

[source,csharp]
----
var nested = container.GetNestedContainer();
var logger = nested.GetInstance<ILogger>();
nested.Dispose();//print Hello form Dispose !
----

Pour illustrer l'isolation nette de se scope il suffit de l'inclure dans une boucle for et de test si la dernière instance récupérer est la même que la courrant :

[source,csharp]
----
ILogger last = null;
for(var i = 0; i < 10; i++)
{
	var nested = container.GetNestedContainer();
	var logger = nested.GetInstance<ILogger>();
	if (last != null)
	Console.WriteLine(last == logger);//print False
	last = logger;
	nested.Dispose();//print Hello form Dispose !
}
----

Au final vraiement rien de compliqué avec le NestedContainer. C'est une technique puissante et très élégante, il suffit juste de clairement définir son Scoping dans une application !

Arriver à ce stade, le tour d'horizon de StructureMap est fait. Vous avez à présent les clés pour le mettre en place dans vos projets. L'exemple qui m'a servit pour écrire l'article est disponible sur Github Gist : https://gist.github.com/Vtek/7e5064a188417713a2d8[ici].

Pour finir je vous propose une dernière petite partie sur les...

== Best practices

Voici un ensemble de règle qu'il est bon de suivre :

* Masquer l'accès au Container : L'inversion de contrôle sert à casser les adhérances. Si vous utilise spécifiquement un Container pour résoudre des dépendances alors c'est l'inverse qui se produit. Il vaut donc mieux passer par une couche intermédiaire qui va vous permettre de changer le Container facileement si besoin.

* Préfére l'injection par constructeur : Elle ne demande pas de logique d'injection et est par conséquent la manière la plus simple de la mettre en place !

* N'oublier pas l'injection par propriété : Certains cas ne sont pas traitable avec l'injection par constructeur, n'hésitez surtout pas à utiliser celle par propriété, elle est là pour ça !

* Ne mélanger pas les cycles de vie : Beaucoup ont essayer et se sont casser les dents. Mise à part le Singleton qui est simple à comprendre et mélanger pour le reste abstenez-vous. En mixant le risque c'est que plus le projet grossit plus la grappe de rétention entre les instances devient complexe. On peut vite arriver à des situations hors de contrôle.

* N'injectez pas des types primitifs : Techniquement c'est faisable mais c'est une erreur. Les dépendant attentend des dépendances définit par des contracts, les types primitifs ne répondent pas cela.

* Ne faite pas de résolution de dépendance base sur des chaines de charactère (Nom, Type, Namespace) : Ici encore techniquement c'est possible mais ça compléxifie la maintenance et ça oblige à revalider la configuration du Container à chaque renommage de dépendance, propriété, namespace. Au final vous allez perdre du temps et il y a d'autre façon de le faire bien plus éfficace.

* Inutile de définir des cycles de vie, il vaut mieux utilisé le Scoping : L'exemple des NestedContainer par de lui même :)

* Ne mélanger pas IoC et Reflexion : C'est une fausse bonne idée. Se sont deux aspects bien différents qui peuvent parfois aboutir au même résultat mais qui n'ont pas du tout la même vocation. Si vous en arriver à faire de la réflexion pour Activer des dépendances dans ce cas là utiliser plutôt https://msdn.microsoft.com/fr-fr/library/dd460648(v=vs.110).aspx[MEF]  . Si se n'est pas le cas alors vous faites très certainement quelque chose qu'un Container IoC fait déjà :)

* Votre container ne devrait jamais être utiliser dans des tests unitaires

* Les dépendances peuvent grossir en nombre rapidement. Pour mieux organiser leur configuration il existe dans StructureMap une classe appelé Registry. Cette classe permet de grouper la configuration par domaine de dépendance.

* Avec l'inversion de contrôle la résolution des dépendances se fait au runtime. C'est donc l'application qui doit tenir les références. Inutile d'essayer de les masquer, vous ne gagnerez pas plus de temps ni de confort de développement !

== Conclusion

S'en est finit pour cette article. J'éspère que vous aurez aprécié la lecture. L'inversion de contrôle est un concept qui de nos jours doit être connu, car de nombreux projets le mettent en oeuvre. StructureMap est un excellente bibliothèque. J'aurais aimé vous présentez de ses capacités mais elles sont tellement nombreuses qu'il faudrait plusieurs articles pour tout traiter !


Update 2017-01-10-Easy-Infrastructure-Testing-with-Goss.adoc

= Easy Infrastructure Testing with Goss
:hp-tags: Configuration Management, Testing, Security, Monitoring, goss
:hp-image: /images/covers/cover-01.jpg

In the world of infrastructure new servers, VMs, containers or applications are often manually validated by a human and some form of build document or checkbox exercise takes place. Even with configuration management, mistakes happen and humans miss things.

Wouldn't it be great if there were an easy way to automatically validate new servers before they are live and we find the problems in production? Well, there is!

== What is Goss?
https://github.com/aelsabbahy/goss[Goss] is a tool that let's you easily test and validate infrastructure. Like http://serverspec.org/[Serverspec] but without all the code. Goss allows you to define what a piece of infrastructure should look like with YAML or JSON. This is made even easier for us with the ability to auto add resources to the Goss configuration.

Goss allows you to validate many different resource types such as files, users, groups, packages, services and http connectivity. You can read the full https://github.com/aelsabbahy/goss/blob/master/docs/manual.md#available-tests[Goss documentation].

== Why Goss?
Well, a few things make goss awesome:

* Written in Go - it's a self contained binary with no dependencies on other libraries or Ruby.
* It's super fast - taking advantages of Go's concurrency model tests are executed and returned almost instantly.
* It's easy to get started with - defining resources in YAML or JSON makes it easy for infrastructure teams who may have limited development experience to get to grips with.

=== An Example
We build a web server running Apache. Someone then checks that Apache `httpd` package is the correct version `2.4.25`, the `deployment` user is in the `www-data` group, there is an application directory at `/srv/www/app`, we check the `httpd` service is running, we can connect to the application at `http://localhost/app` and going to `http://localhost/` gives a `404` error within 1 second.

With Goss, the YAML configuration would look like this:

```yaml
---
package:
  httpd:
    installed: true
    versions:
    - 2.4.25
user:
  deployment:
    exists: true
    groups:
    - deployment
    - www-data
file:
  /srv/www/app:
    exists: true
    filetype: directory
service:
  httpd:
    enabled: true
    running: true
http:
  http://localhost/app:
    status: 200
    timeout: 1000
  http://localhost:
    status: 404
    timeout: 1000
```

That's quite simple, easy to understand what is being validated and can be used to consistently validate all servers of that configuration.

== Server Validation and Monitoring
All this validation of files, processes, services, ports etc... sounds familiar. It's something that we quite often try to achieve with our monitoring tools like Nagios, Zabbix or Sensu.

With Goss we can create a single monitoring check for validation of a piece of infrastructure that may test many resources. Goss has outputs to make this really easy. The nagios_verbose output gives feedback on what is wrong:

image::https://cloud.githubusercontent.com/assets/1253072/18037748/76f65a32-6d83-11e6-9aba-bceabb8430a3.png[Goss - nagios_verbose output]

Server validation can now become part of your monitoring pipeline ensuring that any problems are identified quickly with a single monitoring check.

WARNING: Goss won't replace all of your checks, it doesn't check things like HDD space, RAM usage or errors in log files. But it makes a sweet addition to server / service monitoring.

== HTTP Health Endpoint
It's becoming increasingly common to expose health endpoints for applications and services. Google did this with their Borgmon monitoring system, https://prometheus.io/[Prometheus] also uses this scrape model and there are several libraries out there such as http://metrics.dropwizard.io/3.1.0/[Dropwizard Metrics] or http://blog.kristian.io/django-health-check/[Django Health Check] to name a few.

Goss has a `serve` command that exposes a http endpoint with configurable output such as json. This makes it easy to create a simpe UI or utilise something like https://www.phpservermonitor.org/[PHP Server Monitor] to show the validation status of each piece of infrastructure.

== Final Thoughts

I hope with that with some learning and experimentation you can see the value of validation testing your infrastructure and building validation into your monitoring systems. Goss is a young project and currently only supports Linux although it is active and open to contributions. You can help out by opening issues and pull requests.

I'll follow up with more in-depth posts Goss related posts in the future. Thanks for reading!

= Easy Infrastructure Testing with Goss
:hp-tags: Configuration Management, Testing, Security, Monitoring, goss
:hp-image: covers/cover-01.jpg

In the world of infrastructure new servers, VMs, containers or applications are often manually validated by a human and some form of build document or checkbox exercise takes place. Even with configuration management, mistakes happen and humans miss things.

Wouldn't it be great if there were an easy way to automatically validate new servers before they are live and we find the problems in production? Well, there is!

== What is Goss?
https://github.com/aelsabbahy/goss[Goss] is a tool that let's you easily test and validate infrastructure. Like http://serverspec.org/[Serverspec] but without all the code. Goss allows you to define what a piece of infrastructure should look like with YAML or JSON. This is made even easier for us with the ability to auto add resources to the Goss configuration.

Goss allows you to validate many different resource types such as files, users, groups, packages, services and http connectivity. You can read the full https://github.com/aelsabbahy/goss/blob/master/docs/manual.md#available-tests[Goss documentation].

== Why Goss?
Well, a few things make goss awesome:

* Written in Go - it's a self contained binary with no dependencies on other libraries or Ruby.
* It's super fast - taking advantages of Go's concurrency model tests are executed and returned almost instantly.
* It's easy to get started with - defining resources in YAML or JSON makes it easy for infrastructure teams who may have limited development experience to get to grips with.

=== An Example
We build a web server running Apache. Someone then checks that Apache `httpd` package is the correct version `2.4.25`, the `deployment` user is in the `www-data` group, there is an application directory at `/srv/www/app`, we check the `httpd` service is running, we can connect to the application at `http://localhost/app` and going to `http://localhost/` gives a `404` error within 1 second.

With Goss, the YAML configuration would look like this:

```yaml
---
package:
  httpd:
    installed: true
    versions:
    - 2.4.25
user:
  deployment:
    exists: true
    groups:
    - deployment
    - www-data
file:
  /srv/www/app:
    exists: true
    filetype: directory
service:
  httpd:
    enabled: true
    running: true
http:
  http://localhost/app:
    status: 200
    timeout: 1000
  http://localhost:
    status: 404
    timeout: 1000
```

That's quite simple, easy to understand what is being validated and can be used to consistently validate all servers of that configuration.

== Server Validation and Monitoring
All this validation of files, processes, services, ports etc... sounds familiar. It's something that we quite often try to achieve with our monitoring tools like Nagios, Zabbix or Sensu.

With Goss we can create a single monitoring check for validation of a piece of infrastructure that may test many resources. Goss has outputs to make this really easy. The nagios_verbose output gives feedback on what is wrong:

image::https://cloud.githubusercontent.com/assets/1253072/18037748/76f65a32-6d83-11e6-9aba-bceabb8430a3.png[Goss - nagios_verbose output]

Server validation can now become part of your monitoring pipeline ensuring that any problems are identified quickly with a single monitoring check.

WARNING: Goss won't replace all of your checks, it doesn't check things like HDD space, RAM usage or errors in log files. But it makes a sweet addition to server / service monitoring.

== HTTP Health Endpoint
It's becoming increasingly common to expose health endpoints for applications and services. Google did this with their Borgmon monitoring system, https://prometheus.io/[Prometheus] also uses this scrape model and there are several libraries out there such as http://metrics.dropwizard.io/3.1.0/[Dropwizard Metrics] or http://blog.kristian.io/django-health-check/[Django Health Check] to name a few.

Goss has a `serve` command that exposes a http endpoint with configurable output such as json. This makes it easy to create a simpe UI or utilise something like https://www.phpservermonitor.org/[PHP Server Monitor] to show the validation status of each piece of infrastructure.

== Final Thoughts

I hope with that with some learning and experimentation you can see the value of validation testing your infrastructure and building validation into your monitoring systems. Goss is a young project and currently only supports Linux although it is active and open to contributions. You can help out by opening issues and pull requests.

I'll follow up with more in-depth posts Goss related posts in the future. Thanks for reading!