mirror of
https://github.com/marcel-dempers/docker-development-youtube-series.git
synced 2025-06-06 17:01:30 +00:00
Compare commits
618 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6de04aec72 | ||
|
ed10aa296f | ||
|
e89da26fa4 | ||
|
4135419bbd | ||
|
d103676b6e | ||
|
aecb4c102e | ||
|
d009357aea | ||
|
8ab37d3af3 | ||
|
71b0860e7f | ||
|
0faa84adf7 | ||
|
b1ff3584fe | ||
|
7df717d9cc | ||
|
69f68f4617 | ||
|
3dc59d66f9 | ||
|
f9315b9d33 | ||
|
9c289a54da | ||
|
c5c755b2c7 | ||
|
c78e6701e3 | ||
|
45a85870b8 | ||
|
544db5938a | ||
|
0978619b02 | ||
|
8394659448 | ||
|
6d217aad29 | ||
|
ab284e1ef8 | ||
|
0567bef2a5 | ||
|
6e6be4b3f4 | ||
|
1b8ca7bc4c | ||
|
53cc8ff8bf | ||
|
e7e6b8cf75 | ||
|
39852a2849 | ||
|
76a5becfbe | ||
|
29f6291913 | ||
|
ad009cd1ff | ||
|
5694ff69f1 | ||
|
7c58dc9bdf | ||
|
73c507ad75 | ||
|
205f34545f | ||
|
2943b35a71 | ||
|
cec7b5ddcf | ||
|
177883429c | ||
|
50eba16f40 | ||
|
a1fcfc975f | ||
|
6e80ba9218 | ||
|
ea497429ac | ||
|
6d7959e025 | ||
|
4b4de3272c | ||
|
c7534eea94 | ||
|
3eb2e73c9e | ||
|
a17be8cad9 | ||
|
1d08bd1f9d | ||
|
e398b601e4 | ||
|
b47b9092f4 | ||
|
2393194e97 | ||
|
8d7b27508b | ||
|
c31fc402d8 | ||
|
6ce511cd36 | ||
|
5b36f19e53 | ||
|
f5a6234ef4 | ||
|
f925d502f4 | ||
|
1031bc07c0 | ||
|
3c7dc4e9da | ||
|
2fda7d708f | ||
|
d4cd7c20d4 | ||
|
47b99f55e5 | ||
|
ff1a93d073 | ||
|
5337006f91 | ||
|
a2ae5796d7 | ||
|
f49fff9af5 | ||
|
d37efdde16 | ||
|
3918c32e67 | ||
|
19d2d161b7 | ||
|
1ef3232ea0 | ||
|
c4d93da7f9 | ||
|
23a2846758 | ||
|
9a78d2af15 | ||
|
0c694e5a83 | ||
|
1d046a120d | ||
![]() |
015ab45dc0 | ||
|
827536f63c | ||
![]() |
2c8fa23e1f | ||
![]() |
429df5d66f | ||
|
524ca00611 | ||
|
d703563f43 | ||
|
5e800a5feb | ||
|
3a6be205a5 | ||
|
0cf869ea7c | ||
|
25d04c81c7 | ||
|
cb11199f37 | ||
![]() |
13cadd8f42 | ||
![]() |
c7d7b8e770 | ||
|
8a12d9bdef | ||
![]() |
09fe225846 | ||
![]() |
bba2fd54cc | ||
|
f7cc5ca37e | ||
|
d54e2ef51f | ||
|
a73921605f | ||
|
89556d4cd0 | ||
|
378ae471ed | ||
|
2da868d742 | ||
|
e95f926d5d | ||
|
634ba47ba1 | ||
|
3470911914 | ||
|
689a52bf71 | ||
|
10ef046978 | ||
|
1a6f680e6f | ||
![]() |
c1b3c6bd24 | ||
![]() |
da5f6a05e8 | ||
|
2ce003d6dc | ||
![]() |
9334527d27 | ||
|
8acc5661ad | ||
|
b0c8e2b26c | ||
|
3291f3edbb | ||
|
04d1d86f12 | ||
|
1935a34ead | ||
|
2128acdd1f | ||
|
f8e77439aa | ||
|
26fb40dce6 | ||
|
48eace7204 | ||
|
7fe61a2d6e | ||
|
1795b32d54 | ||
|
9380e336e5 | ||
|
8854b27c20 | ||
|
1532cc5225 | ||
|
80984b7639 | ||
|
9f8decee66 | ||
![]() |
0e934abf5c | ||
|
97ff18e1c0 | ||
|
91d3db711b | ||
|
8cf5032967 | ||
![]() |
e6523900cf | ||
|
a27f4ac11d | ||
|
4fcd8b86be | ||
![]() |
0044a58dd5 | ||
|
253821d863 | ||
|
6098108069 | ||
|
fa0d9332ed | ||
|
3774090ccb | ||
|
000a878d7b | ||
|
b898ab2bf9 | ||
|
51797bede7 | ||
![]() |
1521df9042 | ||
![]() |
4da88ba980 | ||
|
387d4a329f | ||
|
baf786af7b | ||
|
812f2c141c | ||
|
ade7bc2ef4 | ||
|
7ef03d3865 | ||
|
7a89e24437 | ||
|
c740ec50b8 | ||
|
dd832dbdeb | ||
|
b7ff7c45a1 | ||
|
4de5394bf0 | ||
|
bd566f9666 | ||
|
d271b80901 | ||
|
93fef37b6e | ||
|
7125049bab | ||
|
51bf4bd383 | ||
|
ff52e92164 | ||
|
945bed5bbc | ||
|
84ffb46275 | ||
|
1250934379 | ||
|
9f720b1046 | ||
|
e1209446e3 | ||
|
52738af7f4 | ||
|
022ec40bef | ||
|
9a2a27ea06 | ||
|
0e50e4a50f | ||
|
614adf003d | ||
|
e6aa1e60de | ||
|
b96533c6e5 | ||
|
dc163cf341 | ||
|
4095c3050a | ||
|
a9645aa4b2 | ||
|
ee246f3e9f | ||
|
84890e84c3 | ||
|
c484d2f39f | ||
|
9b130c3b65 | ||
|
5a2f656dc8 | ||
|
655764b074 | ||
|
ec8bfc9325 | ||
|
86c1954546 | ||
|
c04feaf772 | ||
|
753d36e402 | ||
|
8bda52fc1d | ||
|
752f395e88 | ||
|
77d46d0d69 | ||
|
bdc235b29c | ||
|
5e97959656 | ||
|
52db04db21 | ||
|
ecf9e5b40f | ||
|
0904659201 | ||
|
51d2e8357a | ||
|
0633fac569 | ||
|
7dcae42633 | ||
|
73430b6694 | ||
|
52e5c975f5 | ||
|
97102574eb | ||
|
74878f5903 | ||
|
8c7c9702f1 | ||
|
20af4e8de1 | ||
|
1915ff6889 | ||
|
a67ef5f2a9 | ||
|
9f9a467ae0 | ||
|
02e0f20e67 | ||
|
ecbf42ce83 | ||
|
7a553d01f0 | ||
|
374c47b244 | ||
|
72ef3db7df | ||
|
fc42e298ba | ||
|
44857147be | ||
|
d9f7a01a15 | ||
|
2d0c1d88e1 | ||
|
e150df6426 | ||
|
143dfaaf97 | ||
|
b5a4621d58 | ||
|
324fd41b5f | ||
|
fcd45bea64 | ||
|
0d637c1046 | ||
|
3d107eacfb | ||
|
563663f309 | ||
|
3724902e8e | ||
|
61303ea8ee | ||
|
f5e528ceca | ||
|
77df499742 | ||
|
71a1fcb7cd | ||
|
7384d560d9 | ||
|
f91b1641d9 | ||
|
8dd82ada77 | ||
|
920b78db41 | ||
|
c23f3a0cdc | ||
|
1cd21b939b | ||
|
4aa9108b48 | ||
|
27ff3314a5 | ||
|
79052248fa | ||
|
4b42ca91c5 | ||
|
ac0e5cf053 | ||
|
99058f0d89 | ||
|
a2d82e4dc4 | ||
|
7335532373 | ||
|
da8749be10 | ||
|
5481fb7c46 | ||
|
a8ba3bba7c | ||
|
4d02dbe534 | ||
|
43b06a684d | ||
|
633f8f2865 | ||
|
769b335b22 | ||
|
cfb70bac24 | ||
|
055d9e7992 | ||
|
36c880fb45 | ||
|
9449f8a2a7 | ||
|
e444489e9b | ||
|
0920c549b2 | ||
|
7f890e1c56 | ||
|
88fbd12e50 | ||
|
6f155829b5 | ||
|
6632544f0c | ||
|
a8c0a0d403 | ||
|
331ca31e48 | ||
|
80fc07a7ec | ||
|
2f802d6c17 | ||
|
100be5c0ae | ||
|
77d3c97644 | ||
|
63ccd689c7 | ||
|
23767a5644 | ||
|
aa45329eb2 | ||
|
f88562ce1f | ||
|
99a7535702 | ||
|
ab38006fc8 | ||
|
e6c40a7f8a | ||
|
12ebd06694 | ||
|
82c52098e7 | ||
|
e43dac88f2 | ||
|
73b9040354 | ||
|
228906ea95 | ||
|
6cc02eaff3 | ||
|
c4fd6e6dea | ||
|
8cf81c68d5 | ||
|
23c10a9572 | ||
|
9ef2d6f454 | ||
|
d52fb4d739 | ||
|
d47976ffa8 | ||
|
9e55e90c97 | ||
|
77c038d44d | ||
|
f796c4bfc6 | ||
|
f48c3bd323 | ||
|
bb3ef94b2d | ||
|
d1ed631367 | ||
|
65d89acefb | ||
|
e707365e75 | ||
|
80773627a3 | ||
|
7c0ffaff4b | ||
|
fcb13a94f6 | ||
|
9f23c5cfbf | ||
|
0009d42207 | ||
|
5de4cf18c8 | ||
|
18d9ac2ca0 | ||
|
9fda23d80b | ||
|
960563164b | ||
|
9fb836b8d4 | ||
|
4021f0f14e | ||
|
fac08a0109 | ||
|
976e397c1f | ||
|
16306a52c4 | ||
|
2f778931f3 | ||
|
55290df2e0 | ||
|
029c91e3e0 | ||
|
fdeec671de | ||
|
ea0634f207 | ||
|
c01029aba3 | ||
|
c96e7afb93 | ||
|
fb2b314836 | ||
|
3ff331078e | ||
|
e4bd0085cc | ||
|
7d7404a8ae | ||
|
fd94ebd341 | ||
|
81476f540d | ||
|
b051ff867f | ||
|
c75fe6c76c | ||
|
b65c9fe1d0 | ||
|
f15521318d | ||
|
f012ac32be | ||
|
1bd98cb9a3 | ||
|
816953d517 | ||
|
0ba3a32a24 | ||
|
6be9fe471b | ||
|
dceb5e2946 | ||
|
274a661bee | ||
|
469d09aa68 | ||
|
8927a39200 | ||
|
2567cbed15 | ||
|
3a006ede01 | ||
|
f224b6f502 | ||
|
cacdf5dc55 | ||
|
8a7a1e3699 | ||
|
7ec37e4826 | ||
|
2877ef0188 | ||
|
4dd0bbd98b | ||
|
d64c813018 | ||
|
24cb687c30 | ||
|
25ca0a292e | ||
|
1774bfcc15 | ||
|
094458c69f | ||
|
e61f0b31b7 | ||
|
6c51b181fc | ||
|
e688761141 | ||
|
f3c8f1fb5c | ||
|
64339c0904 | ||
|
99c045a799 | ||
|
ec38685fcd | ||
|
acf2195e54 | ||
|
55d401ceee | ||
|
bf23d4668c | ||
|
dcd35c6b70 | ||
|
b62ee94b7d | ||
|
9ea23b5c03 | ||
|
633a5d11c0 | ||
|
e183d4b9b8 | ||
|
9f6e038b7c | ||
|
d2278b7e2e | ||
|
e3dfa21ca6 | ||
|
5c86259aff | ||
|
138a127ac2 | ||
|
91839c9822 | ||
|
2059ace2f1 | ||
|
daf73d2cec | ||
|
75e20b239f | ||
|
776e42da90 | ||
|
ab2db8a435 | ||
|
29fb4daa36 | ||
|
b91d28b95d | ||
|
d31cff5cef | ||
|
88a86f681a | ||
|
fa92a6fd70 | ||
|
eb531a57a4 | ||
|
45b2aecdbf | ||
|
1b2bce85fe | ||
|
d39ea77c12 | ||
|
eaec3a66c3 | ||
|
992d78042f | ||
|
f9b9a3f1fe | ||
|
2b4df899b1 | ||
|
921737efb3 | ||
|
d384353ef9 | ||
|
3d563ad550 | ||
|
bbf9ee299d | ||
|
26f7c9c37f | ||
|
63cf075fd7 | ||
|
5e0e4a52bc | ||
|
84d6df644b | ||
|
f9432bf112 | ||
|
341efada7e | ||
|
5b29059681 | ||
|
10aad91e3f | ||
|
f8ac773d6a | ||
|
7483d9b678 | ||
|
a95b9c8c2c | ||
|
5f5c337a81 | ||
|
fce0fb89b2 | ||
|
17ad5d11a7 | ||
|
e641758cf0 | ||
|
28b9def878 | ||
|
cc5d627723 | ||
|
4458691860 | ||
|
28a126dcf9 | ||
|
60c5f6062c | ||
|
81b46c0157 | ||
|
52910fd190 | ||
|
02cdb52eb8 | ||
|
0c8be9ddbd | ||
|
08fa3baaae | ||
|
d157df6ee6 | ||
|
e1a551112d | ||
|
a4a663fb96 | ||
|
507ca6d65f | ||
|
ce513bd875 | ||
|
6508a2ad22 | ||
|
19289fd9cf | ||
|
d51d72f3d2 | ||
|
6012dbaaf8 | ||
|
0ad5d30834 | ||
|
25dd067b44 | ||
|
c78c6a84da | ||
|
eaa40af16e | ||
|
0a6f7ebad0 | ||
|
b0a6b7af40 | ||
|
474770827c | ||
|
5cd2b71a85 | ||
|
b43943a058 | ||
|
83934aa47f | ||
|
ce7d6fb980 | ||
|
8bc1f100f3 | ||
|
23cbd8cf83 | ||
|
6d1cefacfd | ||
|
7eb88e36b2 | ||
|
d59a07521a | ||
|
67c54641ea | ||
|
ad51319efb | ||
|
4093b374a7 | ||
|
bde29bc670 | ||
|
44fcb20b50 | ||
|
cc2eb8680f | ||
|
85f23c51ab | ||
|
fdb7cbc1b5 | ||
|
9e886a82c3 | ||
|
01ac5a8659 | ||
|
d0c430bd82 | ||
|
2798c4bb3f | ||
|
641b16ab12 | ||
|
74ea9524dd | ||
|
a3c8003ec3 | ||
|
ffcfb28c4a | ||
|
d45a1e3e1d | ||
|
af6a960c17 | ||
|
09af3d9d51 | ||
|
59d07f9544 | ||
|
903cb6ec38 | ||
|
25daf0ff5a | ||
|
8e03dd55c2 | ||
|
4b8f92df85 | ||
|
2b14f61e87 | ||
|
89c82195fc | ||
|
744f2e42cb | ||
|
e8c05bbe4e | ||
|
22df30dc9a | ||
|
80ee1c1c33 | ||
|
ac1743d0d4 | ||
|
f62a79be33 | ||
|
b798a4f702 | ||
|
0c8a7c68cb | ||
|
9f53661cfc | ||
|
0e9e83ed7e | ||
|
52be982157 | ||
|
837f69f019 | ||
|
877810d147 | ||
|
56e7eb473f | ||
|
7d9aa53bbd | ||
|
eb3e655bb3 | ||
|
c0bb603bb9 | ||
|
dabbc618ef | ||
|
539918c3ea | ||
|
de227f02cc | ||
|
befe92eb39 | ||
|
e97d63db55 | ||
|
5c64dd7c9a | ||
|
4b9dd849cd | ||
|
52f655f881 | ||
|
a7f169b884 | ||
|
91ea892b36 | ||
|
97535fc3d7 | ||
|
9230a00222 | ||
|
7128b76adf | ||
|
61d29d8ac1 | ||
|
8fd9595943 | ||
|
c5fc304df9 | ||
|
198273d14a | ||
|
978f3dea56 | ||
|
3ddba6bf8f | ||
|
bc355559de | ||
|
2d3577d1fd | ||
|
94e0e8a8a5 | ||
|
6ca7e2ffb2 | ||
|
8969aea0ba | ||
|
1c9e8d26a0 | ||
|
c03a1b608d | ||
|
0aca4d7f5d | ||
|
6a66fa75f7 | ||
|
123be244e6 | ||
|
45cd191f3c | ||
|
209e3dc041 | ||
|
a8bad89fc0 | ||
|
fda8b41724 | ||
|
8020ca3dcd | ||
|
97652a6f14 | ||
|
7c4a86d7bb | ||
|
3889efcd86 | ||
|
bcf8e55856 | ||
|
f67f5e919a | ||
|
02db1d7098 | ||
|
b2aae260a3 | ||
|
09223e2dcb | ||
|
dbacbffe7b | ||
|
bc96847851 | ||
|
4a71879521 | ||
|
151375082e | ||
|
b811a5723a | ||
|
7a756c20a9 | ||
|
96827d3543 | ||
|
4027722182 | ||
|
9d008aa662 | ||
|
d11a11754f | ||
|
cc780b2df3 | ||
|
4ae1661ece | ||
|
9cbd210704 | ||
|
f0c8be9554 | ||
|
c5214cffed | ||
|
6cd1038893 | ||
|
63f9945d2a | ||
|
a8a7c120c1 | ||
|
86a146a02c | ||
|
33e7e8dfd2 | ||
|
876232d53c | ||
|
ac3aed8fb9 | ||
|
a6ea3373aa | ||
|
772ef7e985 | ||
|
26b1cc8d2d | ||
|
eeeb23d625 | ||
|
b7f939855d | ||
|
b279955aff | ||
|
c12a0f1443 | ||
|
e711ed29fa | ||
|
2415cb33b2 | ||
|
428fc51e76 | ||
|
f2c3647b7d | ||
|
aebfab763c | ||
|
8d893d63d8 | ||
|
e454f2783b | ||
|
1f6ccdc4bb | ||
|
151b9817fd | ||
|
d11a812ec6 | ||
|
93ff675562 | ||
|
63c6d0910d | ||
|
18f8d8f586 | ||
|
513c7736e1 | ||
|
9a4c55a38a | ||
|
4f3b493bf4 | ||
|
b72228ed94 | ||
|
c5e73e22d4 | ||
|
6ebb169ee2 | ||
|
33907264a2 | ||
|
03fc7912b8 | ||
|
e26582502a | ||
|
c237f66ca1 | ||
|
7a3d3b7858 | ||
|
37c562c2c1 | ||
|
41e5e3332c | ||
|
6c0f87f911 | ||
|
850595939c | ||
|
149db75516 | ||
|
50117a6a2b | ||
|
6eda362268 | ||
|
256912c484 | ||
|
b67c5d4297 | ||
|
ada19a5590 | ||
|
18113996d3 | ||
|
4548b01086 | ||
|
7f0e7435d1 | ||
|
1c41fe3e40 | ||
|
6a43fbe890 | ||
|
bfebd40a76 | ||
|
969c35524e | ||
|
e19376f909 | ||
|
e66d185444 | ||
|
1dd2779121 | ||
|
96ff1cb1f8 | ||
|
20011946e7 | ||
|
a8664272e9 | ||
|
04b9870255 | ||
|
33752dd072 | ||
|
32877a10f7 | ||
|
aca91ac997 | ||
|
17394dde98 | ||
|
8bb97ec9e7 | ||
|
a906ff68b7 | ||
|
a0d7e2ad36 | ||
|
c42c5f9c27 | ||
|
a7e24785c7 | ||
|
fef3ee8630 | ||
|
017d05aa17 | ||
|
ed398d3742 | ||
|
0e2ad9f05d | ||
|
cba88d61db | ||
|
914b6f4ab9 | ||
|
0147ab746f | ||
|
f06b5886e7 | ||
|
f38c38ec6b | ||
|
8da3b2ed32 | ||
|
bbe4bd13a4 | ||
|
79c6748acd |
42
.devcontainer/devcontainer.json
Normal file
42
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,42 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
|
||||
// https://github.com/microsoft/vscode-dev-containers/tree/v0.162.0/containers/javascript-node
|
||||
{
|
||||
"name": "kubernetes-tutorial-basic",
|
||||
"build": {
|
||||
"dockerfile": "dockerfile",
|
||||
"args": { "KIND_VERSION": "0.14.0" }
|
||||
},
|
||||
"mounts": ["type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock"],
|
||||
|
||||
// Set *default* container specific settings.json values on container create.
|
||||
"settings": {
|
||||
"terminal.integrated.shell.linux": "/bin/sh"
|
||||
},
|
||||
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
// "extensions": [
|
||||
// "dbaeumer.vscode-eslint"
|
||||
// ],
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
// "postCreateCommand": "yarn install",
|
||||
|
||||
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "root"
|
||||
}
|
||||
|
||||
//Notes:
|
||||
// Set site url for wordpress
|
||||
|
||||
// mysql -u exampleuser -p exampledb
|
||||
|
||||
// UPDATE wp_options
|
||||
// SET option_value = 'https://docker-development-youtube-series-4rjv9rg7hqrg9-80.githubpreview.dev'
|
||||
// WHERE option_name = 'home';
|
||||
|
||||
// UPDATE wp_options
|
||||
// SET option_value = 'https://docker-development-youtube-series-4rjv9rg7hqrg9-80.githubpreview.dev'
|
||||
// WHERE option_name = 'siteurl';
|
12
.devcontainer/dockerfile
Normal file
12
.devcontainer/dockerfile
Normal file
@ -0,0 +1,12 @@
|
||||
FROM alpine:latest
|
||||
|
||||
ARG KIND_VERSION=0.14.0
|
||||
|
||||
RUN apk add --no-cache curl docker-cli
|
||||
|
||||
RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
|
||||
RUN chmod +x ./kubectl
|
||||
RUN mv ./kubectl /usr/local/bin/kubectl
|
||||
|
||||
RUN curl -L https://github.com/kubernetes-sigs/kind/releases/download/v${KIND_VERSION}/kind-linux-amd64 -o /usr/local/bin/kind && \
|
||||
chmod +x /usr/local/bin/kind
|
37
.github/workflows/docker._yaml
vendored
Normal file
37
.github/workflows/docker._yaml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
###########################################################
|
||||
# Rename the file extension to ".yaml" (remove "_") to enable
|
||||
###########################################################
|
||||
|
||||
name: Docker Series Builds
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: docker login
|
||||
env:
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: |
|
||||
docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
||||
- name: docker build csharp
|
||||
run: |
|
||||
docker build ./c# -t aimvector/csharp:1.0.0
|
||||
- name: docker build nodejs
|
||||
run: |
|
||||
docker build ./nodejs -t aimvector/nodejs:1.0.0
|
||||
- name: docker build python
|
||||
run: |
|
||||
docker build ./python -t aimvector/python:1.0.0
|
||||
- name: docker build golang
|
||||
run: |
|
||||
docker build ./golang -t aimvector/golang:1.0.0
|
||||
- name: docker push
|
||||
run: |
|
||||
docker push aimvector/csharp:1.0.0
|
||||
docker push aimvector/nodejs:1.0.0
|
||||
docker push aimvector/golang:1.0.0
|
||||
docker push aimvector/python:1.0.0
|
20
.github/workflows/self-hosted-runner._yaml
vendored
Normal file
20
.github/workflows/self-hosted-runner._yaml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
###########################################################
|
||||
# IMPORTANT -> Rename the file extension to ".yaml" (remove "_") to enable this
|
||||
###########################################################
|
||||
|
||||
name: Self-Hosted Runner Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- <branch-name>
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: docker build python
|
||||
run: |
|
||||
docker build ./python/introduction/ -t python:1.0.0
|
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
c#/src/bin/
|
||||
c#/src/obj/
|
||||
node_modules/
|
||||
__pycache__/
|
||||
*.pem
|
||||
*.csr
|
||||
.terraform
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
security/letsencrypt/introduction/certs/**
|
||||
kubernetes/shipa/installs/shipa-helm-chart-1.1.1/
|
||||
messaging/kafka/data/*
|
||||
kubernetes/portainer/volume*
|
||||
kubernetes/rancher/volume/*
|
||||
kubernetes/portainer/business/volume*
|
||||
|
||||
#ignore postgres data for sample and database tutorials
|
||||
pgdata
|
||||
|
||||
#ignore sample data mount points
|
||||
.data
|
58
.vscode/launch.json
vendored
Normal file
58
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": ".NET Core Docker Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickRemoteProcess}",
|
||||
"pipeTransport": {
|
||||
"pipeProgram": "docker",
|
||||
"pipeArgs": [ "exec", "-i", "csharp" ],
|
||||
"debuggerPath": "/root/vsdbg/vsdbg",
|
||||
"pipeCwd": "${workspaceRoot}",
|
||||
"quoteArgs": false
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/work": "${workspaceRoot}/c#/src/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Remote Docker",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "remote",
|
||||
"remotePath":"/go/src/work/",
|
||||
"port": 2345,
|
||||
"host": "127.0.0.1",
|
||||
"program": "${workspaceFolder}/golang/src/",
|
||||
"args": [],
|
||||
"trace" : "verbose",
|
||||
"env" : {}
|
||||
},
|
||||
{
|
||||
"name": "Python Attach",
|
||||
"type": "python",
|
||||
"request": "attach",
|
||||
"pathMappings": [
|
||||
{
|
||||
"localRoot": "${workspaceFolder}/python/src/",
|
||||
"remoteRoot": "/work"
|
||||
}
|
||||
],
|
||||
"port": 5678,
|
||||
"host": "127.0.0.1"
|
||||
},
|
||||
{
|
||||
"name": "Docker: Attach to Node",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"remoteRoot": "/work/src/",
|
||||
"port": 9229,
|
||||
"address": "localhost",
|
||||
"localRoot": "${workspaceFolder}/nodejs/src/",
|
||||
"protocol": "inspector",
|
||||
"restart": true
|
||||
}
|
||||
]
|
||||
}
|
38
README.md
38
README.md
@ -1,9 +1,37 @@
|
||||
# docker-development-youtube-series
|
||||
# The Ultimate Engineer Toolbox <img src="https://www.shareicon.net/data/128x128/2017/04/11/883708_media_512x512.png" alt="YouTube" width="5%" height="5%"> :hammer::wrench:
|
||||
|
||||
Hi!
|
||||
A Collection of tools, hands-on walkthroughs with source code. <br/>
|
||||
The Ultimate Swiss Army knife for DevOps, Developers and Platform Engineers
|
||||
|
||||
This is the source code for the YouTube series covering docker-based development workflows.
|
||||
<br/>
|
||||
|
||||
Part #1 https://youtu.be/wyjNpxLRmLg
|
||||
|
||||
More details coming soon!
|
||||
| Steps | Playlist :tv: | Source :octocat: |
|
||||
|---|---|---|
|
||||
| [Learn Kubernetes](./kubernetes/README.md) :snowflake: | <a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVvUEdqaBeMK2awVThNujwMd" title="Kubernetes"><img src="https://i.ytimg.com/vi/8h4FoWK7tIA/hqdefault.jpg" width="50%" alt="Kubernetes Guide" /></a> | [source](./kubernetes/readme.md) |
|
||||
| Learn about CI/CD tools :whale: | <a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVsSsrnZimHEf7NJ1MlRhQUj" title="CI/CD"><img src="https://i.ytimg.com/vi/myCcJJ_Fk10/hqdefault.jpg" width="50%" alt="CI/CD Guide" /></a> | | | |
|
||||
| Deploy Kubernetes to the cloud :partly_sunny: | <a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVsUhzNBkn-rPzXtPNpJu1-k" title="Cloud K8s"><img src="https://i.ytimg.com/vi/3jA9EfkSAUU/hqdefault.jpg" width="50%" alt="Cloud Guide" /></a> | [source](./kubernetes/cloud/readme.md) |
|
||||
| Monitoring Kubernetes :mag: | <a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVuEXCrRkPFWLXRKWNLOVUHn" title="Cloud K8s"><img src="https://i.ytimg.com/vi/5o37CGlNLr8/hqdefault.jpg" width="50%" alt="Cloud Guide" /></a> | [source](./monitoring/prometheus/kubernetes/readme.md) |
|
||||
| Guide to Logging :page_with_curl: | <a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVvfDxFW50Mdezk0xum-tyHT" title="Cloud K8s"><img src="https://i.ytimg.com/vi/MMVdkzeQ848/hqdefault.jpg" width="50%" alt="Cloud Guide" /></a> | [source](./monitoring/logging/readme.md) |
|
||||
| Guide to ServiceMesh :globe_with_meridians: | <a href="https://www.youtube.com/playlist?list=PLHq1uqvAteVsmxHpGsMjTOROn3i99lzTA" title="Cloud K8s"><img src="https://i.ytimg.com/vi/rVNPnHeGYBE/hqdefault.jpg" width="50%" alt="Cloud Guide" /></a> | [source](./kubernetes/servicemesh/readme.md) |
|
||||
|
||||
|
||||
## Docker Development Basics
|
||||
|
||||
|
||||
| Step :heavy_check_mark: | Video :movie_camera: | Source Code :octocat: |
|
||||
|---|---|---|
|
||||
| Working with `Dockerfiles` <br/>(.NET, Golang, Python, NodeJS) | <a href="https://youtu.be/wyjNpxLRmLg" title="Docker 1"><img src="https://i.ytimg.com/vi/wyjNpxLRmLg/hqdefault.jpg" width="50%" alt="Docker 1" /></a> | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/part1) |
|
||||
| Working with code <br/>(.NET, Golang, Python, NodeJS) | <a href="https://youtu.be/EdmKENqnQUw" title="Docker 1"><img src="https://i.ytimg.com/vi/EdmKENqnQUw/hqdefault.jpg" width="50%" alt="Docker 1" /></a> | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/part2) |
|
||||
| Docker Multistage explained | <a href="https://youtu.be/2lQ7WrwpZfI" title="Docker 1"><img src="https://i.ytimg.com/vi/2lQ7WrwpZfI/hqdefault.jpg" width="50%" alt="Docker 1" /></a> | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/part3) |
|
||||
| Debugging Go in Docker | <a href="https://youtu.be/kToyI16IFxs" title="Docker 1"><img src="https://i.ytimg.com/vi/kToyI16IFxs/hqdefault.jpg" width="50%" alt="Docker 1" /></a> | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/master/golang) |
|
||||
| Debugging .NET in Docker | <a href="https://youtu.be/ds2bud0ZYTY" title="Docker 1"><img src="https://i.ytimg.com/vi/ds2bud0ZYTY/hqdefault.jpg" width="50%" alt="Docker 1" /></a> | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/part5) |
|
||||
| Debugging Python in Docker | <a href="https://youtu.be/b78Tg-YmJZI" title="Docker 1"><img src="https://i.ytimg.com/vi/b78Tg-YmJZI/hqdefault.jpg" width="50%" alt="Docker 1" /></a> | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/debugging-python) |
|
||||
| Debugging NodeJS in Docker | <a href="https://youtu.be/ktvgr9VZ4dc" title="Docker 1"><img src="https://i.ytimg.com/vi/ktvgr9VZ4dc/hqdefault.jpg" width="50%" alt="Docker 1" /></a> | [source](https://github.com/marcel-dempers/docker-development-youtube-series/tree/master/nodejs) |
|
||||
|
||||
## Engineering Toolbox :hammer::wrench:
|
||||
|
||||
|
||||
Checkout the toolbox [website](https://marceldempers.dev/toolbox)
|
||||
|
||||
<a href="https://marceldempers.dev/toolbox" title="toolbox 1"><img src="./toolbox.png" alt="toolbox 1" /></a>
|
||||
|
151
ai/openai/introduction/README.md
Normal file
151
ai/openai/introduction/README.md
Normal file
@ -0,0 +1,151 @@
|
||||
# Introduction to Open AI
|
||||
|
||||
## Overview
|
||||
|
||||
What is [Open AI](https://openai.com/) ?
|
||||
|
||||
* Research company on AI development
|
||||
* Builds and provides models
|
||||
* Builds and provides a standard protocol for using AI
|
||||
|
||||
What is a model ?
|
||||
|
||||
I see a model as a language super database. </br>
|
||||
Instead of writing a query, that is slow to query a traditional database like SQL, you can throw a question at a model and it gives you an answer really fast </br>
|
||||
|
||||
Model examples:
|
||||
* GPT 3.5
|
||||
* GPT 4
|
||||
|
||||
## Getting started
|
||||
|
||||
The best way to get started and to understand OpenAI, is to learn hands on
|
||||
|
||||
* Create an OpenAI account [here](https://openai.com/)
|
||||
|
||||
## Chat GPT
|
||||
|
||||
Here you can find the link to [ChatGPT](https://chat.openai.com/)
|
||||
|
||||
## Open AI Playground
|
||||
|
||||
Here you can find the link to the [OpenAI Playground](https://platform.openai.com/playground)
|
||||
|
||||
## Build an AI powered app
|
||||
|
||||
We can start with a `main.py` that reads a message
|
||||
|
||||
```
|
||||
import sys
|
||||
|
||||
message = sys.argv[0]
|
||||
|
||||
```
|
||||
Then we will need the code from the Open AI playground and add it to our `main.py`. </br>
|
||||
Move the `import` statements to the top </br>
|
||||
|
||||
Once you have tidied up everything, you can get the response message from the AI:
|
||||
|
||||
```
|
||||
responseMessage = response.choices[0].message.content
|
||||
```
|
||||
|
||||
Let's build our app
|
||||
|
||||
```
|
||||
cd ai\openai\introduction
|
||||
docker build . -t ai-app
|
||||
```
|
||||
|
||||
Set my OpenAI API key
|
||||
|
||||
```
|
||||
$ENV:OPENAI_API_KEY=""
|
||||
```
|
||||
|
||||
Run our AI App:
|
||||
|
||||
```
|
||||
docker run -it -e OPENAI_API_KEY=$ENV:OPENAI_API_KEY ai-app
|
||||
```
|
||||
|
||||
When we run the app, notice it has no concept of memory. </br>
|
||||
The playground works because it keeps track of all the user and AI messages and keeps appending new messages to it </br>
|
||||
So it can track the conversation.
|
||||
|
||||
Let's keep track of messages, by writing it to a local file </br>
|
||||
We will also take the system message out and keep it as a constant in our code </br>
|
||||
|
||||
Full example:
|
||||
|
||||
```
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import openai
|
||||
|
||||
openai.api_key = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
#read the incoming message
|
||||
message = sys.argv[1]
|
||||
user_message = {
|
||||
"role" : "user",
|
||||
"content" : message
|
||||
}
|
||||
|
||||
systemMessage = {
|
||||
"role": "system",
|
||||
"content": "You are a kubernetes exper that can assist developers with troubleshooting deployments\n\nTo help the developer you will need to know the namespaces as well as the pod name. Ask for missing information\n\nGenerate a command to help the developer surface logs or information\n"
|
||||
}
|
||||
|
||||
# read the cached user messages if there are any
|
||||
userMessages = []
|
||||
if os.path.isfile("messages.json"):
|
||||
with open('messages.json', newline='') as messagesFile:
|
||||
data = messagesFile.read()
|
||||
userMessages = json.loads(data)
|
||||
|
||||
# add the new message to it and update the cached messages
|
||||
userMessages.append(user_message)
|
||||
with open('messages.json', 'w', newline='') as messagesFile:
|
||||
msgJSON = json.dumps(userMessages)
|
||||
messagesFile.write(msgJSON)
|
||||
print(msgJSON)
|
||||
|
||||
messages = []
|
||||
messages.append(systemMessage)
|
||||
messages.extend(userMessages)
|
||||
|
||||
response = openai.ChatCompletion.create(
|
||||
model="gpt-3.5-turbo",
|
||||
messages=messages,
|
||||
temperature=1,
|
||||
max_tokens=256,
|
||||
top_p=1,
|
||||
frequency_penalty=0,
|
||||
presence_penalty=0
|
||||
)
|
||||
|
||||
responseMessage = response.choices[0].message.content
|
||||
print(responseMessage)
|
||||
|
||||
```
|
||||
|
||||
Now we can mount our volume so we persist the cache of messages
|
||||
|
||||
```
|
||||
docker run -it -e OPENAI_API_KEY=$ENV:OPENAI_API_KEY -v ${PWD}:/app ai-app "can you help me with my deployment?"
|
||||
Of course! I'd be happy to help with your deployment. Could you please provide me with the namespace and the name of the pod you're encountering issues with?
|
||||
|
||||
docker run -it -e OPENAI_API_KEY=$ENV:OPENAI_API_KEY -v ${PWD}:/app ai-app "my pod is pod-123"
|
||||
Sure, I can help you with your deployment. Can you please provide me with the namespace in which the pod is running?
|
||||
|
||||
docker run -it -e OPENAI_API_KEY=$ENV:OPENAI_API_KEY -v ${PWD}:/app ai-app "its in the products namespace"
|
||||
Great! To surface the logs for the pod "pod-123" in the "products" namespace, you can use the following command:
|
||||
|
||||
```shell
|
||||
kubectl logs -n products pod-123
|
||||
```
|
||||
|
||||
This command will retrieve the logs for the specified pod in the given namespace. Make sure you have the necessary permissions to access the namespace.
|
||||
```
|
11
ai/openai/introduction/dockerfile
Normal file
11
ai/openai/introduction/dockerfile
Normal file
@ -0,0 +1,11 @@
|
||||
FROM python:3.11-alpine
|
||||
|
||||
RUN mkdir /app
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt /app/requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY main.py /app/
|
||||
|
||||
ENTRYPOINT ["python3", "main.py"]
|
49
ai/openai/introduction/main.py
Normal file
49
ai/openai/introduction/main.py
Normal file
@ -0,0 +1,49 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import openai
|
||||
|
||||
openai.api_key = os.getenv("OPENAI_API_KEY")
|
||||
|
||||
#read the incoming message
|
||||
message = sys.argv[1]
|
||||
user_message = {
|
||||
"role" : "user",
|
||||
"content" : message
|
||||
}
|
||||
|
||||
systemMessage = {
|
||||
"role": "system",
|
||||
"content": "You are a kubernetes exper that can assist developers with troubleshooting deployments\n\nTo help the developer you will need to know the namespaces as well as the pod name. Ask for missing information\n\nGenerate a command to help the developer surface logs or information\n"
|
||||
}
|
||||
|
||||
# read the cached user messages if there are any
|
||||
userMessages = []
|
||||
if os.path.isfile("messages.json"):
|
||||
with open('messages.json', newline='') as messagesFile:
|
||||
data = messagesFile.read()
|
||||
userMessages = json.loads(data)
|
||||
|
||||
# add the new message to it and update the cached messages
|
||||
userMessages.append(user_message)
|
||||
with open('messages.json', 'w', newline='') as messagesFile:
|
||||
msgJSON = json.dumps(userMessages)
|
||||
messagesFile.write(msgJSON)
|
||||
print(msgJSON)
|
||||
|
||||
messages = []
|
||||
messages.append(systemMessage)
|
||||
messages.extend(userMessages)
|
||||
|
||||
response = openai.ChatCompletion.create(
|
||||
model="gpt-3.5-turbo",
|
||||
messages=messages,
|
||||
temperature=1,
|
||||
max_tokens=256,
|
||||
top_p=1,
|
||||
frequency_penalty=0,
|
||||
presence_penalty=0
|
||||
)
|
||||
|
||||
responseMessage = response.choices[0].message.content
|
||||
print(responseMessage)
|
1
ai/openai/introduction/requirements.txt
Normal file
1
ai/openai/introduction/requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
openai==0.28.0
|
4
apache/kafka/README.md
Normal file
4
apache/kafka/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Introduction to Kafka
|
||||
|
||||
This guide is under the messaging section alongside other message brokers like `RabbitMQ` etc. </br>
|
||||
Checkout the guide under the [messaging/kafka](../../messaging/kafka/README.md) folder
|
3
argo/argo-cd/README.md
Normal file
3
argo/argo-cd/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Introduction to Argo CD
|
||||
|
||||
<a href="https://youtu.be/2WSJF7d8dUg" title="argo"><img src="https://i.ytimg.com/vi/2WSJF7d8dUg/hqdefault.jpg" width="20%" alt="introduction to argo cd" /></a>
|
20
argo/argo-cd/app.yaml
Normal file
20
argo/argo-cd/app.yaml
Normal file
@ -0,0 +1,20 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: example-app
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/marcel-dempers/docker-development-youtube-series.git
|
||||
targetRevision: HEAD
|
||||
path: argo/example-app
|
||||
directory:
|
||||
recurse: true
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: example-app
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: false
|
||||
selfHeal: false
|
28
argo/argo-cd/create-in-cluster-app.md
Normal file
28
argo/argo-cd/create-in-cluster-app.md
Normal file
@ -0,0 +1,28 @@
|
||||
|
||||
CLI
|
||||
```
|
||||
argocd app create --name test \
|
||||
--repo https://github.com/marcel-dempers/docker-development-youtube-series \
|
||||
--dest-server https://kubernetes.default.svc \
|
||||
--dest-namespace marcel --path kubernetes
|
||||
```
|
||||
|
||||
YAML
|
||||
|
||||
```
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: test
|
||||
namespace: marcel
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/marcel-dempers/docker-development-youtube-series.git
|
||||
targetRevision: HEAD
|
||||
path: argo/example-app
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: marcel
|
||||
```
|
||||
|
2870
argo/argo-cd/install.yaml
Normal file
2870
argo/argo-cd/install.yaml
Normal file
File diff suppressed because it is too large
Load Diff
10
argo/example-app/configmaps/configmap.yaml
Normal file
10
argo/example-app/configmaps/configmap.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: example-config
|
||||
data:
|
||||
config.json: |
|
||||
{
|
||||
"environment" : "dev"
|
||||
}
|
||||
# kubectl create configmap example-config --from-file ./golang/configs/config.json
|
47
argo/example-app/deployments/deployment.yaml
Normal file
47
argo/example-app/deployments/deployment.yaml
Normal file
@ -0,0 +1,47 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: example-deploy
|
||||
labels:
|
||||
app: example-app
|
||||
annotations:
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: example-app
|
||||
replicas: 2
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: example-app
|
||||
spec:
|
||||
containers:
|
||||
- name: example-app
|
||||
image: aimvector/python:1.0.0
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 5000
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
||||
volumeMounts:
|
||||
- name: secret-volume
|
||||
mountPath: /secrets/
|
||||
- name: config-volume
|
||||
mountPath: /configs/
|
||||
volumes:
|
||||
- name: secret-volume
|
||||
secret:
|
||||
secretName: mysecret
|
||||
- name: config-volume
|
||||
configMap:
|
||||
name: example-config #name of our configmap object
|
4
argo/example-app/namespaces/example-app.yaml
Normal file
4
argo/example-app/namespaces/example-app.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: example-app
|
12
argo/example-app/secrets/secret.yaml
Normal file
12
argo/example-app/secrets/secret.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: mysecret
|
||||
type: Opaque
|
||||
stringData:
|
||||
secret.json: |-
|
||||
{
|
||||
"api_key" : "somesecretgoeshere"
|
||||
}
|
||||
|
||||
#kubectl create secret generic mysecret --from-file .\golang\secrets\secret.json
|
11
argo/example-app/services/service.yaml
Normal file
11
argo/example-app/services/service.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: example-service
|
||||
spec:
|
||||
selector:
|
||||
app: example-app
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 5000
|
@ -1 +1,27 @@
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:2.2
|
||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 as build
|
||||
|
||||
#install debugger for NET Core
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y unzip
|
||||
RUN curl -sSL https://aka.ms/getvsdbgsh | /bin/sh /dev/stdin -v latest -l ~/vsdbg
|
||||
|
||||
RUN mkdir /src/
|
||||
WORKDIR /src/
|
||||
|
||||
COPY ./src/helloworld.csproj /src/helloworld.csproj
|
||||
RUN dotnet restore
|
||||
|
||||
COPY ./src/ /src/
|
||||
RUN mkdir /out/
|
||||
|
||||
RUN dotnet build helloworld.csproj --configuration Debug --no-restore
|
||||
RUN dotnet publish helloworld.csproj --output /out --configuration Debug --no-restore
|
||||
|
||||
ENTRYPOINT ["dotnet", "run"]
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:8.0 as runtime
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=build /out/ /app
|
||||
|
||||
ENTRYPOINT ["dotnet", "helloworld.dll"]
|
||||
|
26
c#/src/Program.cs
Normal file
26
c#/src/Program.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
builder.Services.Configure<HostOptions>(builder.Configuration.GetSection("HostOptions"));
|
||||
|
||||
builder.WebHost.ConfigureKestrel(serverOptions =>
|
||||
{
|
||||
serverOptions.ListenAnyIP(5000);
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
Console.WriteLine(Environment.ProcessorCount.ToString());
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.MapGet("/", async () =>
|
||||
{
|
||||
await Task.Delay(40000); // Sleep for 40 seconds
|
||||
return "Hello World!";
|
||||
});
|
||||
|
||||
app.Run();
|
31
c#/src/Properties/launchSettings.json
Normal file
31
c#/src/Properties/launchSettings.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:59079",
|
||||
"sslPort": 44372
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"helloworld": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7158;http://localhost:5122",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
c#/src/appsettings.Development.json
Normal file
8
c#/src/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
13
c#/src/helloworld.csproj
Normal file
13
c#/src/helloworld.csproj
Normal file
@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
3
deno/README.md
Normal file
3
deno/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Introduction to Deno with Docker
|
||||
|
||||
<a href="https://youtu.be/4EfnECkCx8E" title="Kubernetes"><img src="https://i.ytimg.com/vi/4EfnECkCx8E/hqdefault.jpg" width="20%" alt="introduction to deno" /></a>
|
13
deno/deno-multistage.dockerfile
Normal file
13
deno/deno-multistage.dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM aimvector/deno:1.0.0-buster-slim as build
|
||||
|
||||
COPY ./src/ $DENO_DIR
|
||||
|
||||
RUN mkdir /out/
|
||||
RUN deno bundle /deno-dir/server.js /out/server.js
|
||||
|
||||
FROM aimvector/deno:1.0.0-buster-slim as final
|
||||
|
||||
COPY --from=build /out/server.js /deno-dir/server.js
|
||||
|
||||
ENTRYPOINT ["deno"]
|
||||
CMD ["run", "--allow-net", "/deno-dir/server.js"]
|
25
deno/deno.dockerfile
Normal file
25
deno/deno.dockerfile
Normal file
@ -0,0 +1,25 @@
|
||||
FROM debian:buster-slim
|
||||
|
||||
ENV DENO_VERSION=1.0.0
|
||||
|
||||
RUN apt-get -qq update \
|
||||
&& apt-get -qq install -y --no-install-recommends curl ca-certificates unzip \
|
||||
&& curl -fsSL https://github.com/denoland/deno/releases/download/v${DENO_VERSION}/deno-x86_64-unknown-linux-gnu.zip \
|
||||
--output deno.zip \
|
||||
&& unzip deno.zip \
|
||||
&& rm deno.zip \
|
||||
&& chmod 777 deno \
|
||||
&& mv deno /usr/bin/deno \
|
||||
&& apt-get -qq remove --purge -y curl ca-certificates unzip \
|
||||
&& apt-get -y -qq autoremove \
|
||||
&& apt-get -qq clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
RUN useradd --uid 1993 --user-group deno \
|
||||
&& mkdir /deno-dir/ \
|
||||
&& chown deno:deno /deno-dir/
|
||||
|
||||
ENV DENO_DIR /deno-dir/
|
||||
|
||||
ENTRYPOINT ["deno"]
|
||||
CMD ["run", "https://deno.land/std/examples/welcome.ts"]
|
35
deno/deployment/deployment.yaml
Normal file
35
deno/deployment/deployment.yaml
Normal file
@ -0,0 +1,35 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: example-deploy
|
||||
labels:
|
||||
app: example-app
|
||||
annotations:
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: example-app
|
||||
replicas: 2
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: example-app
|
||||
spec:
|
||||
containers:
|
||||
- name: example-app
|
||||
image: aimvector/deno-app:v1
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 5000
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
cpu: "10m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "500m"
|
15
deno/deployment/service.yaml
Normal file
15
deno/deployment/service.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: example-service
|
||||
labels:
|
||||
app: example-app
|
||||
spec:
|
||||
type: LoadBalancer
|
||||
selector:
|
||||
app: example-app
|
||||
ports:
|
||||
- protocol: TCP
|
||||
name: http
|
||||
port: 80
|
||||
targetPort: 5000
|
6
deno/dockerfile
Normal file
6
deno/dockerfile
Normal file
@ -0,0 +1,6 @@
|
||||
FROM aimvector/deno:1.0.0-buster-slim as build
|
||||
|
||||
COPY ./src/ $DENO_DIR
|
||||
|
||||
ENTRYPOINT ["deno"]
|
||||
CMD ["run", "--allow-net", "/deno-dir/server.js"]
|
5
deno/src/server.js
Normal file
5
deno/src/server.js
Normal file
@ -0,0 +1,5 @@
|
||||
import { serve } from "https://deno.land/std@0.50.0/http/server.ts";
|
||||
|
||||
for await (const req of serve({ port: 5000 })) {
|
||||
req.respond({ body: "Hello World! - from Deno 1.0.0\n" });
|
||||
}
|
@ -1,18 +1,71 @@
|
||||
version: "3"
|
||||
services:
|
||||
csharp:
|
||||
container_name: csharp
|
||||
image: aimvector/csharp:1.0.0
|
||||
build: ./c#
|
||||
golang:
|
||||
container_name: golang
|
||||
image: aimvector/golang:1.0.0
|
||||
build: ./golang
|
||||
nodejs:
|
||||
container_name: nodejs
|
||||
image: aimvector/nodejs:1.0.0
|
||||
build: ./nodejs
|
||||
python:
|
||||
container_name: python
|
||||
image: aimvector/python:1.0.0
|
||||
build: ./python
|
||||
version: "3.4"
|
||||
services:
|
||||
csharp: #docker run --rm -it -v ${PWD}:/work -w /work -p 5000:5000 aimvector/csharp:1.0.0 /bin/sh
|
||||
container_name: csharp
|
||||
image: aimvector/csharp:1.0.0
|
||||
build:
|
||||
context: ./c#
|
||||
target: debug
|
||||
volumes:
|
||||
- ./c#/src/:/work/
|
||||
ports:
|
||||
- 5000:5000
|
||||
golang: #docker run --rm -it -v ${PWD}:/go/src/work -v ${PWD}/golang/configs/:/configs -v ${PWD}/golang/secrets/:/secrets -p 5001:5000 -p 2345:2345 --security-opt "seccomp:unconfined" aimvector/golang:1.0.0
|
||||
container_name: golang
|
||||
image: aimvector/golang:1.0.0
|
||||
build:
|
||||
context: ./golang
|
||||
target: prod
|
||||
volumes:
|
||||
- ./golang/configs:/configs/
|
||||
- ./golang/secrets:/secrets/
|
||||
- ./golang/src/:/go/src/work/
|
||||
ports:
|
||||
- 5001:5000
|
||||
- 2345:2345
|
||||
security_opt:
|
||||
- "seccomp:unconfined"
|
||||
python: #docker run --rm -it -v ${PWD}:/work -w /work -p 5003:5000 aimvector/python:1.0.0 /bin/sh
|
||||
container_name: python
|
||||
image: aimvector/python:1.0.0
|
||||
build:
|
||||
context: ./python
|
||||
target: debug
|
||||
#working_dir: /work #comment out for build.target:prod
|
||||
#entrypoint: /bin/sh #comment out for build.target:prod
|
||||
#stdin_open: true #comment out for build.target:prod
|
||||
#tty: true #comment out for build.target:prod
|
||||
volumes:
|
||||
- ./python/src/:/work
|
||||
ports:
|
||||
- 5003:5000
|
||||
- 5678:5678
|
||||
nodejs: #docker run --rm -it -v ${PWD}:/work -w /work -p 5002:5000 aimvector/nodejs:1.0.0 /bin/sh
|
||||
container_name: nodejs
|
||||
image: aimvector/nodejs:1.0.0
|
||||
build:
|
||||
context: ./nodejs
|
||||
target: debug
|
||||
#working_dir: /work #comment out for build.target:prod
|
||||
#entrypoint: /bin/sh #comment out for build.target:prod
|
||||
#stdin_open: true #comment out for build.target:prod
|
||||
#tty: true #comment out for build.target:prod
|
||||
volumes:
|
||||
- ./nodejs/src/:/work/src/
|
||||
ports:
|
||||
- 5002:5000
|
||||
- 9229:9229 #debug port
|
||||
spring-java: #docker run --rm -it -v ${PWD}:/work -w /work -p 5002:5000 aimvector/nodejs:1.0.0 /bin/sh
|
||||
container_name: spring-java
|
||||
image: aimvector/spring-java:1.0.0
|
||||
build:
|
||||
context: ./springboot/java/
|
||||
target: debug
|
||||
working_dir: /work/src/ #comment out for build.target:prod
|
||||
entrypoint: /bin/sh #comment out for build.target:prod
|
||||
stdin_open: true #comment out for build.target:prod
|
||||
tty: true #comment out for build.target:prod
|
||||
volumes:
|
||||
- ./springboot/java/:/work/src/
|
||||
ports:
|
||||
- 9999:8080
|
||||
|
3
drone-ci/README.md
Normal file
3
drone-ci/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Introduction to Drone CI
|
||||
|
||||
<a href="https://youtu.be/myCcJJ_Fk10" title="drone ci"><img src="https://i.ytimg.com/vi/myCcJJ_Fk10/hqdefault.jpg" width="20%" alt="introduction to drone ci" /></a>
|
32
drone-ci/drone.yml
Normal file
32
drone-ci/drone.yml
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: kubernetes
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: build-push
|
||||
image: docker:dind
|
||||
volumes:
|
||||
- name: dockersock
|
||||
path: /var/run
|
||||
environment:
|
||||
DOCKER_USER:
|
||||
from_secret: DOCKER_USER
|
||||
DOCKER_PASSWORD:
|
||||
from_secret: DOCKER_PASSWORD
|
||||
commands:
|
||||
- sleep 5 ## give docker enough time to start
|
||||
- docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
|
||||
- docker build ./golang -t aimvector/golang:1.0.0
|
||||
- docker push aimvector/golang:1.0.0
|
||||
|
||||
services:
|
||||
- name: docker
|
||||
image: docker:dind
|
||||
privileged: true
|
||||
volumes:
|
||||
- name: dockersock
|
||||
path: /var/run
|
||||
volumes:
|
||||
- name: dockersock
|
||||
temp: {}
|
50
drone-ci/postgres/postgres.yaml
Normal file
50
drone-ci/postgres/postgres.yaml
Normal file
@ -0,0 +1,50 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: postgres-config
|
||||
labels:
|
||||
app: postgres
|
||||
data:
|
||||
POSTGRES_DB: postgresdb
|
||||
POSTGRES_USER: postgresadmin
|
||||
POSTGRES_PASSWORD: admin123
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: postgres
|
||||
spec:
|
||||
serviceName: "postgres"
|
||||
selector:
|
||||
matchLabels:
|
||||
app: postgres
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: postgres
|
||||
spec:
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:10.4
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
ports:
|
||||
- containerPort: 5432
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: postgres-config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: postgres
|
||||
labels:
|
||||
app: postgres
|
||||
spec:
|
||||
selector:
|
||||
app: postgres
|
||||
ports:
|
||||
- protocol: TCP
|
||||
name: http
|
||||
port: 5432
|
||||
targetPort: 5432
|
40
drone-ci/runner/dronerunner-rbac.yaml
Normal file
40
drone-ci/runner/dronerunner-rbac.yaml
Normal file
@ -0,0 +1,40 @@
|
||||
kind: Role
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
namespace: drone
|
||||
name: drone-runner
|
||||
rules:
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- secrets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- apiGroups:
|
||||
- ""
|
||||
resources:
|
||||
- pods
|
||||
- pods/log
|
||||
verbs:
|
||||
- get
|
||||
- create
|
||||
- delete
|
||||
- list
|
||||
- watch
|
||||
- update
|
||||
|
||||
---
|
||||
kind: RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
metadata:
|
||||
name: drone-runner
|
||||
namespace: drone
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: drone-runner
|
||||
namespace: drone
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: drone-runner
|
||||
apiGroup: rbac.authorization.k8s.io
|
43
drone-ci/runner/dronerunner.yaml
Normal file
43
drone-ci/runner/dronerunner.yaml
Normal file
@ -0,0 +1,43 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: drone-runner
|
||||
labels:
|
||||
app.kubernetes.io/name: drone-runner
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: drone
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: drone
|
||||
spec:
|
||||
serviceAccountName: drone-runner
|
||||
containers:
|
||||
- name: runner
|
||||
image: drone/drone-runner-kube:latest
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
env:
|
||||
- name: DRONE_NAMESPACE_DEFAULT
|
||||
value: drone
|
||||
- name: DRONE_SERVICE_ACCOUNT_DEFAULT
|
||||
value: drone-runner
|
||||
- name: DRONE_RPC_HOST
|
||||
value: droneserver.drone
|
||||
- name: DRONE_RPC_PROTO
|
||||
value: http
|
||||
- name: DRONE_RPC_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: drone-server-secret
|
||||
key: DRONE_RPC_SECRET
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: drone-runner
|
||||
labels:
|
||||
app.kubernetes.io/name: drone-runner
|
64
drone-ci/server/droneserver-deployment.yaml
Normal file
64
drone-ci/server/droneserver-deployment.yaml
Normal file
@ -0,0 +1,64 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: drone-server
|
||||
labels:
|
||||
app: drone-server
|
||||
annotations:
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: drone-server
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: drone-server
|
||||
spec:
|
||||
containers:
|
||||
- name: drone-server
|
||||
image: drone/drone:1.6.5
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- containerPort: 443
|
||||
env:
|
||||
- name: DRONE_USER_CREATE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: drone-server-secret
|
||||
key: DRONE_USER_CREATE
|
||||
- name: DRONE_DATABASE_DRIVER
|
||||
value: postgres
|
||||
- name: DRONE_DATABASE_DATASOURCE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: drone-server-secret
|
||||
key: DRONE_DATABASE_DATASOURCE
|
||||
- name: DRONE_SERVER_PROTO
|
||||
value: https
|
||||
- name: DRONE_SERVER_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: drone-server-secret
|
||||
key: DRONE_SERVER_HOST
|
||||
- name: DRONE_GITHUB_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: drone-server-secret
|
||||
key: DRONE_GITHUB_CLIENT_ID
|
||||
- name: DRONE_GITHUB_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: drone-server-secret
|
||||
key: DRONE_GITHUB_CLIENT_SECRET
|
||||
- name: DRONE_RPC_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: drone-server-secret
|
||||
key: DRONE_RPC_SECRET
|
18
drone-ci/server/droneserver-ingress.yaml
Normal file
18
drone-ci/server/droneserver-ingress.yaml
Normal file
@ -0,0 +1,18 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: drone-server
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "traefik"
|
||||
traefik.ingress.kubernetes.io/frontend-entry-points: http,https
|
||||
traefik.ingress.kubernetes.io/redirect-entry-point: https
|
||||
traefik.ingress.kubernetes.io/redirect-permanent: "true"
|
||||
spec:
|
||||
rules:
|
||||
- host: drone.marceldempers.dev
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
serviceName: droneserver
|
||||
servicePort: 80
|
||||
path: /
|
12
drone-ci/server/droneserver-secret.yaml
Normal file
12
drone-ci/server/droneserver-secret.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: drone-server-secret
|
||||
type: Opaque
|
||||
data:
|
||||
DRONE_GITHUB_CLIENT_ID: xxxxxxx #Get this from GitHub OAUTH
|
||||
DRONE_GITHUB_CLIENT_SECRET: xxxxxxx #Get this from GitHub OAUTH
|
||||
DRONE_RPC_SECRET: xxxxxxx #openssl rand -hex 16
|
||||
DRONE_DATABASE_DATASOURCE: xxxxxxx #postgres://postgresadmin:admin123@postgres:5432/postgresdb?sslmode=disable
|
||||
DRONE_USER_CREATE: xxxxxxx #username:marcel-dempers,admin:true
|
||||
DRONE_SERVER_HOST: xxxxxxx #drone.marceldempers.dev
|
19
drone-ci/server/droneserver-service.yaml
Normal file
19
drone-ci/server/droneserver-service.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: droneserver
|
||||
labels:
|
||||
app: drone-server
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: drone-server
|
||||
ports:
|
||||
- protocol: TCP
|
||||
name: http
|
||||
port: 80
|
||||
targetPort: 80
|
||||
- protocol: TCP
|
||||
name: https
|
||||
port: 443
|
||||
targetPort: 443
|
84
github/actions/self-hosted-runner/README.md
Normal file
84
github/actions/self-hosted-runner/README.md
Normal file
@ -0,0 +1,84 @@
|
||||
<a href="https://youtu.be/RcHGqCBofvw" title="githubactions"><img src="https://i.ytimg.com/vi/RcHGqCBofvw/hqdefault.jpg" width="20%" alt="introduction to github actions runners" /></a>
|
||||
|
||||
# Introduction to GitHub Actions: Self hosted runners
|
||||
|
||||
## Create a kubernetes cluster
|
||||
|
||||
In this guide we we''ll need a Kubernetes cluster for testing. Let's create one using [kind](https://kind.sigs.k8s.io/) </br>
|
||||
|
||||
```
|
||||
kind create cluster --name githubactions --image kindest/node:v1.28.0@sha256:b7a4cad12c197af3ba43202d3efe03246b3f0793f162afb40a33c923952d5b31
|
||||
```
|
||||
|
||||
Let's test our cluster:
|
||||
```
|
||||
kubectl get nodes
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
githubactions-control-plane Ready control-plane 2m53s v1.28.0
|
||||
```
|
||||
|
||||
## Running the Runner in Docker
|
||||
|
||||
We can simply install this directly on to virtual machines , but for this demo, I'd like to run it in Kubernetes inside a container. </br>
|
||||
|
||||
### Security notes
|
||||
|
||||
* Running in Docker needs high priviledges.
|
||||
* Would not recommend to use these on public repositories.
|
||||
* Would recommend to always run your CI systems in seperate Kubernetes clusters.
|
||||
|
||||
### Creating a Dockerfile
|
||||
|
||||
* Installing Docker CLI
|
||||
For this to work we need a `dockerfile` and follow instructions to [Install Docker](https://docs.docker.com/engine/install/debian/).
|
||||
I would grab the content and create statements for my `dockerfile` </br>
|
||||
|
||||
Now notice that we only install the `docker` CLI. </br>
|
||||
This is because we want our running to be able to run docker commands , but the actual docker server runs elsewhere </br>
|
||||
This gives you flexibility to tighten security by running docker on the host itself and potentially run the container runtime in a non-root environment </br>
|
||||
|
||||
* Installing Github Actions Runner
|
||||
|
||||
Next up we will need to install the [GitHub actions runner](https://github.com/actions/runner) in our `dockerfile`
|
||||
Now to give you a "behind the scenes" of how I usually build my `dockerfile`s, I run a container to test my installs:
|
||||
|
||||
```
|
||||
docker build . -t github-runner:latest
|
||||
docker run -it github-runner bash
|
||||
```
|
||||
|
||||
Next steps:
|
||||
|
||||
* Now we can see `docker` is installed
|
||||
* To see how a runner is installed, lets go to our repo | runner and click "New self-hosted runner"
|
||||
* Try these steps in the container
|
||||
* We will needfew dependencies
|
||||
* We download the runner
|
||||
* TODO
|
||||
|
||||
|
||||
Finally lets test the runner in `docker`
|
||||
|
||||
```
|
||||
docker run -it -e GITHUB_PERSONAL_TOKEN="" -e GITHUB_OWNER=marcel-dempers -e GITHUB_REPOSITORY=docker-development-youtube-series github-runner
|
||||
```
|
||||
|
||||
## Deploy to Kubernetes
|
||||
|
||||
Load our github runner image so we dont need to push it to a registry:
|
||||
|
||||
```
|
||||
kind load docker-image github-runner:latest --name githubactions
|
||||
```
|
||||
|
||||
Create a kubernetes secret with our github details
|
||||
|
||||
```
|
||||
kubectl create ns github
|
||||
kubectl -n github create secret generic github-secret `
|
||||
--from-literal GITHUB_OWNER=marcel-dempers `
|
||||
--from-literal GITHUB_REPOSITORY=docker-development-youtube-series `
|
||||
--from-literal GITHUB_PERSONAL_TOKEN=""
|
||||
|
||||
kubectl -n github apply -f kubernetes.yaml
|
||||
```
|
46
github/actions/self-hosted-runner/dockerfile
Normal file
46
github/actions/self-hosted-runner/dockerfile
Normal file
@ -0,0 +1,46 @@
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ARG RUNNER_VERSION="2.302.1"
|
||||
|
||||
ENV GITHUB_PERSONAL_TOKEN ""
|
||||
ENV GITHUB_OWNER ""
|
||||
ENV GITHUB_REPOSITORY ""
|
||||
|
||||
# Install Docker -> https://docs.docker.com/engine/install/debian/
|
||||
|
||||
# Add Docker's official GPG key:
|
||||
RUN apt-get update && \
|
||||
apt-get install -y ca-certificates curl gnupg
|
||||
RUN install -m 0755 -d /etc/apt/keyrings
|
||||
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
RUN chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
|
||||
# Add the repository to Apt sources:
|
||||
RUN echo \
|
||||
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
|
||||
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
|
||||
tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
RUN apt-get update
|
||||
|
||||
# I only install the CLI, we will run docker in another container!
|
||||
RUN apt-get install -y docker-ce-cli
|
||||
|
||||
# Install the GitHub Actions Runner
|
||||
RUN apt-get update && apt-get install -y sudo jq
|
||||
|
||||
RUN useradd -m github && \
|
||||
usermod -aG sudo github && \
|
||||
echo "%sudo ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
|
||||
|
||||
USER github
|
||||
WORKDIR /actions-runner
|
||||
RUN curl -Ls https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz | tar xz \
|
||||
&& sudo ./bin/installdependencies.sh
|
||||
|
||||
COPY --chown=github:github entrypoint.sh /actions-runner/entrypoint.sh
|
||||
RUN sudo chmod u+x /actions-runner/entrypoint.sh
|
||||
|
||||
#working folder for the runner
|
||||
RUN sudo mkdir /work
|
||||
|
||||
ENTRYPOINT ["/actions-runner/entrypoint.sh"]
|
26
github/actions/self-hosted-runner/entrypoint.sh
Normal file
26
github/actions/self-hosted-runner/entrypoint.sh
Normal file
@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
registration_url="https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPOSITORY}/actions/runners/registration-token"
|
||||
echo "Requesting registration URL at '${registration_url}'"
|
||||
|
||||
payload=$(curl -sX POST -H "Authorization: token ${GITHUB_PERSONAL_TOKEN}" ${registration_url})
|
||||
export RUNNER_TOKEN=$(echo $payload | jq .token --raw-output)
|
||||
|
||||
./config.sh \
|
||||
--name $(hostname) \
|
||||
--token ${RUNNER_TOKEN} \
|
||||
-- labels my-runner \
|
||||
--url https://github.com/${GITHUB_OWNER}/${GITHUB_REPOSITORY} \
|
||||
--work "/work" \
|
||||
--unattended \
|
||||
--replace
|
||||
|
||||
remove() {
|
||||
./config.sh remove --unattended --token "${RUNNER_TOKEN}"
|
||||
}
|
||||
|
||||
trap 'remove; exit 130' INT
|
||||
trap 'remove; exit 143' TERM
|
||||
|
||||
./run.sh "$*" &
|
||||
|
||||
wait $!
|
64
github/actions/self-hosted-runner/kubernetes.yaml
Normal file
64
github/actions/self-hosted-runner/kubernetes.yaml
Normal file
@ -0,0 +1,64 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: github-runner
|
||||
labels:
|
||||
app: github-runner
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: github-runner
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: github-runner
|
||||
spec:
|
||||
containers:
|
||||
- name: github-runner
|
||||
imagePullPolicy: Never #use local kind image
|
||||
image: github-runner:latest
|
||||
env:
|
||||
- name: GITHUB_OWNER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: github-secret
|
||||
key: GITHUB_OWNER
|
||||
- name: GITHUB_REPOSITORY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: github-secret
|
||||
key: GITHUB_REPOSITORY
|
||||
- name: GITHUB_PERSONAL_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: github-secret
|
||||
key: GITHUB_PERSONAL_TOKEN
|
||||
- name: DOCKER_HOST
|
||||
value: tcp://localhost:2375
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /work/
|
||||
- name: dind
|
||||
image: docker:24.0.6-dind
|
||||
env:
|
||||
- name: DOCKER_TLS_CERTDIR
|
||||
value: ""
|
||||
resources:
|
||||
requests:
|
||||
cpu: 20m
|
||||
memory: 512Mi
|
||||
securityContext:
|
||||
privileged: true
|
||||
volumeMounts:
|
||||
- name: docker-graph-storage
|
||||
mountPath: /var/lib/docker
|
||||
- name: data
|
||||
mountPath: /work/
|
||||
volumes:
|
||||
- name: docker-graph-storage
|
||||
emptyDir: {}
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
|
||||
|
3
golang/configs/config.json
Normal file
3
golang/configs/config.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"environment" : "dev"
|
||||
}
|
3
golang/dlv.sh
Normal file
3
golang/dlv.sh
Normal file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
cd /go/src/work
|
||||
dlv debug --headless --log -l 0.0.0.0:2345 --api-version=2
|
@ -1 +1,32 @@
|
||||
FROM golang:1.12.5-alpine3.9 as builder
|
||||
FROM golang:1.12.5-alpine3.9 as debug
|
||||
|
||||
# installing git
|
||||
RUN apk update && apk upgrade && \
|
||||
apk add --no-cache git \
|
||||
dpkg \
|
||||
gcc \
|
||||
git \
|
||||
musl-dev
|
||||
|
||||
ENV GOPATH /go
|
||||
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
|
||||
|
||||
RUN go get github.com/sirupsen/logrus
|
||||
RUN go get github.com/buaazp/fasthttprouter
|
||||
RUN go get github.com/valyala/fasthttp
|
||||
RUN go get github.com/go-delve/delve/cmd/dlv
|
||||
|
||||
WORKDIR /go/src/work
|
||||
COPY ./src /go/src/work/
|
||||
|
||||
RUN go build -o app
|
||||
### Run the Delve debugger ###
|
||||
COPY ./dlv.sh /
|
||||
RUN chmod +x /dlv.sh
|
||||
ENTRYPOINT [ "/dlv.sh"]
|
||||
|
||||
###########START NEW IMAGE###################
|
||||
|
||||
FROM alpine:3.9 as prod
|
||||
COPY --from=debug /go/src/work/app /
|
||||
CMD ./app
|
||||
|
36
golang/introduction/app/app.go
Normal file
36
golang/introduction/app/app.go
Normal file
@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
customers := GetCustomers()
|
||||
|
||||
for _, customer := range customers {
|
||||
//we can access the "customer" variable in this approach
|
||||
fmt.Println(customer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func getData() (customers []string) {
|
||||
|
||||
customers = []string{ "Marcel Dempers", "Bob Smith", "John Smith"}
|
||||
|
||||
customers = append(customers, "Ben Spain")
|
||||
customers = append(customers, "Aleem Janmohamed")
|
||||
customers = append(customers, "Jamie le Notre")
|
||||
customers = append(customers, "Victor Savkov")
|
||||
customers = append(customers, "P The Admin")
|
||||
customers = append(customers, "Adrian Oprea")
|
||||
customers = append(customers, "Jonathan D")
|
||||
|
||||
for _, customer := range customers {
|
||||
|
||||
fmt.Println(customer)
|
||||
}
|
||||
|
||||
return customers
|
||||
}
|
28
golang/introduction/app/customers.go
Normal file
28
golang/introduction/app/customers.go
Normal file
@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
type (
|
||||
Customer struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
FullName string
|
||||
}
|
||||
)
|
||||
|
||||
func GetCustomers()(customers []Customer) {
|
||||
|
||||
marcel := Customer{ FirstName: "Marcel", LastName: "Dempers"}
|
||||
|
||||
customers = append(customers, marcel,
|
||||
Customer{ FirstName: "Ben", LastName: "Spain" },
|
||||
Customer{ FirstName: "Aleem", LastName: "Janmohamed" },
|
||||
Customer{ FirstName: "Jamie", LastName: "le Notre" },
|
||||
Customer{ FirstName: "Victor", LastName: "Savkov" },
|
||||
Customer{ FirstName: "P", LastName: "The Admin" },
|
||||
Customer{ FirstName: "Adrian", LastName: "Oprea" },
|
||||
Customer{ FirstName: "Jonathan", LastName: "D" },
|
||||
|
||||
)
|
||||
|
||||
return customers
|
||||
|
||||
}
|
3
golang/introduction/app/go.mod
Normal file
3
golang/introduction/app/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module github.com/docker-development-youtube-series/golang/introdouction/app
|
||||
|
||||
go 1.15
|
13
golang/introduction/dockerfile
Normal file
13
golang/introduction/dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM golang:1.15-alpine as dev
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
FROM golang:1.15-alpine as build
|
||||
|
||||
WORKDIR /app
|
||||
COPY ./app/* /app/
|
||||
RUN go build -o app
|
||||
|
||||
FROM alpine as runtime
|
||||
COPY --from=build /app/app /
|
||||
CMD ./app
|
3
golang/introduction/part-2.json/dockerfile
Normal file
3
golang/introduction/part-2.json/dockerfile
Normal file
@ -0,0 +1,3 @@
|
||||
FROM golang:1.15-alpine as dev
|
||||
|
||||
WORKDIR /work
|
326
golang/introduction/part-2.json/readme.md
Normal file
326
golang/introduction/part-2.json/readme.md
Normal file
@ -0,0 +1,326 @@
|
||||
# Introduction to Go: JSON
|
||||
|
||||
<a href="https://youtu.be/_ok29xwZ11k" title="golang-part-2"><img src="https://i.ytimg.com/vi/_ok29xwZ11k/hqdefault.jpg" width="20%" alt="introduction to Go part 2" /></a>
|
||||
|
||||
In programming languages, you will very often deal with data structures internally. <br/>
|
||||
Sometimes, you need to pass data outside of your application or read data from another application, or even a file. <br/>
|
||||
|
||||
API, for example often expose data in `json`, `xml`, `grpc` and all sorts of formats.
|
||||
Before we can really take a look at building APIs and even storing data in databases,
|
||||
we need some fundamental knowledge about how to convert these data structures, into structures
|
||||
that our application can understand. <br/>
|
||||
|
||||
In part 1, we dealt with [Variables]("https://tour.golang.org/basics/8") and more importantly, <br/> we dealt with `struct` data type, which made us build a `customer` object.
|
||||
|
||||
If we wanted to pass the `customer` to a database, or an external system, it's often required that we convert this to `json` format.
|
||||
|
||||
|
||||
## Dev Environment
|
||||
|
||||
The same as Part 1, we start with a [dockerfile](./dockerfile) where we declare our version of `go`.
|
||||
|
||||
```
|
||||
cd golang\introduction\part-2.json
|
||||
|
||||
docker build --target dev . -t go
|
||||
docker run -it -v ${PWD}:/work go sh
|
||||
go version
|
||||
```
|
||||
|
||||
## Create our App
|
||||
|
||||
Create a new directory that holds defines our `repository` and holds our `module`
|
||||
|
||||
```
|
||||
mkdir videos
|
||||
|
||||
```
|
||||
|
||||
* Define a module path
|
||||
|
||||
```
|
||||
# change directory to your application source code
|
||||
|
||||
cd videos
|
||||
|
||||
# create a go module file
|
||||
|
||||
go mod init videos
|
||||
|
||||
```
|
||||
|
||||
## Create our base code
|
||||
|
||||
We start out all our applications with a `main.go` defining our `package`, declaring our `import` dependencies <br/>
|
||||
and our entrypoint `main()` function
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, world.")
|
||||
}
|
||||
```
|
||||
|
||||
## Create our Videos app
|
||||
|
||||
Firstly, we create a seperate code file `videos.go` that deals with our YouTube videos <br/>
|
||||
|
||||
Similar to Part 1, we define the file, we define what a video looks like using a `struct` <br/>
|
||||
and a function for returning a slice of videos.
|
||||
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import ()
|
||||
|
||||
type video struct {
|
||||
Id string
|
||||
Title string
|
||||
Description string
|
||||
Imageurl string
|
||||
Url string
|
||||
}
|
||||
|
||||
func getVideos()(videos []video){
|
||||
//Get our videos here,
|
||||
//and return them
|
||||
return videos
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Populating our Video Struct
|
||||
|
||||
Similar to our customers app, we created a `struct` and populated it with data:
|
||||
Let's add the following to our `getVideos()` function:
|
||||
|
||||
```
|
||||
video1 := video{
|
||||
Id : "eyvLwK5C2dw",
|
||||
Title : "Kubernetes on Azure",
|
||||
Imageurl : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw",
|
||||
Url : "https://youtu.be/eyvLwK5C2dw",
|
||||
Description : "",
|
||||
}
|
||||
|
||||
video2 := video{
|
||||
Id : "QThadS3Soig",
|
||||
Title : "Kubernetes on Amazon",
|
||||
Imageurl : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg",
|
||||
Url : "https://youtu.be/QThadS3Soig",
|
||||
Description : "",
|
||||
}
|
||||
|
||||
return []video{ video1, video2}
|
||||
|
||||
```
|
||||
|
||||
We need to also invoke our function that will return the videos
|
||||
Don't worry about where the videos will come from. <br/>
|
||||
We always start with the building blocks and move on from there <br/>
|
||||
|
||||
In our `main()` function: <br/>
|
||||
|
||||
```
|
||||
func main() {
|
||||
videos := getVideos()
|
||||
fmt.Println(videos)
|
||||
}
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
Now, Ideally, we do not want to "hard code" our videos like this. <br/>
|
||||
Currently, our videos are embedded into the code and we can only return 2 videos. <br/>
|
||||
If we want to introduce a new video, we have to rebuild the application. <br/>
|
||||
|
||||
In the real world, videos are our data. <br/>
|
||||
Data lives outside of the application. Like in a database.
|
||||
|
||||
Technically, we can use a database, but that is too big of a step for now. <br/>
|
||||
So let's start with a file instead.
|
||||
|
||||
### Introducing ioutil
|
||||
|
||||
It's important to learn how to navigate and read go documentation.
|
||||
Let's go to https://golang.org/pkg/io/ioutil/
|
||||
|
||||
We can import this library since its part of the Go standard library set.
|
||||
|
||||
```
|
||||
import (
|
||||
"io/ioutil"
|
||||
)
|
||||
```
|
||||
|
||||
Let's move our videos into a `json` file called `videos.json` :
|
||||
|
||||
```
|
||||
[
|
||||
{
|
||||
"id" : "QThadS3Soig",
|
||||
"title" : "Kubernetes on Amazon",
|
||||
"imageurl" : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg",
|
||||
"url" : "https://youtu.be/QThadS3Soig",
|
||||
"description" : ""
|
||||
},
|
||||
{
|
||||
"id" : "eyvLwK5C2dw",
|
||||
"title" : "Kubernetes on Azure",
|
||||
"imageurl" : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw",
|
||||
"url" : "https://youtu.be/eyvLwK5C2dw",
|
||||
"description" : ""
|
||||
}
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
## Reading a file
|
||||
|
||||
Let's take another look at https://golang.org/pkg/io/ioutil/ <br/>
|
||||
Notice the `ReadFile` function
|
||||
We can read a file from disk:
|
||||
|
||||
```
|
||||
fileBytes, err := ioutil.ReadFile("./videos.json")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fileContent := string(fileBytes)
|
||||
fmt.Println(fileContent)
|
||||
|
||||
```
|
||||
|
||||
## Panic
|
||||
|
||||
Also notice the function `panic`. <br/>
|
||||
|
||||
"A panic typically means something went unexpectedly wrong. Mostly we use it to fail fast on errors that shouldn’t occur during normal operation, or that we aren’t prepared to handle gracefully."
|
||||
- https://gobyexample.com/panic
|
||||
|
||||
For now, we will panic on every potential error. <br/>
|
||||
In a future video, we'll cover Error handling in more depth
|
||||
|
||||
## JSON
|
||||
|
||||
Working with JSON is pretty straightforward in go. <br/>
|
||||
`struct` objects are pretty well synergized with `json`
|
||||
|
||||
Let's take a look at the `json` package that is part of go.
|
||||
https://golang.org/pkg/encoding/json/
|
||||
|
||||
```
|
||||
"encoding/json"
|
||||
```
|
||||
|
||||
This package allows us to marshal and unmarshal JSON. <br/>
|
||||
This allows conversion between a `struct` or a go type, and `json`
|
||||
|
||||
|
||||
## Unmarshal
|
||||
|
||||
From JSON(bytes) ==> Struct\Go type
|
||||
|
||||
```
|
||||
err = json.Unmarshal(fileBytes, &videos)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return videos
|
||||
```
|
||||
## Using our Data
|
||||
|
||||
Now in the real world, you would use your data to do something. <br/>
|
||||
Let's say we'd like to update some common terms and conditions to the video description <br/>
|
||||
|
||||
In our `main` function, let's loop the videos & update the description.
|
||||
Feel free to checkout Part 1 of the series on `loops`!
|
||||
|
||||
```
|
||||
for _, video := range videos {
|
||||
}
|
||||
```
|
||||
|
||||
Now we dont want to override the description, we want to inject the terms and conditions on a new line
|
||||
|
||||
```
|
||||
video.Description = video.Description + "\n Remember to Like & Subscribe!"
|
||||
```
|
||||
|
||||
Note that when we run this, it does not print our new description field </br>
|
||||
This is because the loop `range` is giving us a copy of the video object. </br>
|
||||
This means we are updating a copy, but printing out the original. </br>
|
||||
We need to use the loop indexer and update the original object in the slice. </br>
|
||||
|
||||
|
||||
```
|
||||
for i, _ := range videos {
|
||||
videos[i].Description = videos[i].Description + "\n Remember to Like & Subscribe!"
|
||||
}
|
||||
```
|
||||
|
||||
Run our app again and now it updates the original video!
|
||||
|
||||
## Marshal
|
||||
|
||||
https://golang.org/pkg/encoding/json/#Marshal
|
||||
From Struct\Go type ==> JSON(bytes)
|
||||
|
||||
Let's create a new function for saving our video back to file
|
||||
|
||||
```
|
||||
func saveVideos(videos []video)(){
|
||||
|
||||
videoBytes, err := json.Marshal(videos)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Writing to File
|
||||
|
||||
https://golang.org/pkg/io/ioutil/
|
||||
|
||||
```
|
||||
|
||||
err = ioutil.WriteFile("./videos-updated.json", videoBytes, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
```
|
||||
|
||||
Then in our `main` function we can save our videos after the update:
|
||||
|
||||
```
|
||||
saveVideos(videos)
|
||||
```
|
||||
|
||||
## Mapping fields
|
||||
|
||||
|
||||
In our example our `json` and our struct fields match exactly.
|
||||
Sometimes this is not possible to maintain.
|
||||
Sometimes we need to tell go, to map certain `json` fields to certain fields in our `struct`
|
||||
We can do it like so:
|
||||
|
||||
```
|
||||
type video struct {
|
||||
Id string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Imageurl string `json:"imageurl"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
```
|
3
golang/introduction/part-2.json/videos/go.mod
Normal file
3
golang/introduction/part-2.json/videos/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module videos
|
||||
|
||||
go 1.15
|
17
golang/introduction/part-2.json/videos/main.go
Normal file
17
golang/introduction/part-2.json/videos/main.go
Normal file
@ -0,0 +1,17 @@
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
|
||||
videos := getVideos()
|
||||
|
||||
for i, _ := range videos {
|
||||
videos[i].Description = videos[i].Description + "\n Remember to Like & Subscribe!"
|
||||
}
|
||||
|
||||
fmt.Println(videos)
|
||||
|
||||
saveVideos(videos)
|
||||
|
||||
}
|
16
golang/introduction/part-2.json/videos/videos-updated.json
Normal file
16
golang/introduction/part-2.json/videos/videos-updated.json
Normal file
@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"id": "QThadS3Soig",
|
||||
"title": "Kubernetes on Amazon",
|
||||
"description": "\n Remember to Like \u0026 Subscribe!",
|
||||
"imageurl": "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg",
|
||||
"url": "https://youtu.be/QThadS3Soig"
|
||||
},
|
||||
{
|
||||
"id": "eyvLwK5C2dw",
|
||||
"title": "Kubernetes on Azure",
|
||||
"description": "\n Remember to Like \u0026 Subscribe!",
|
||||
"imageurl": "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF\u0026rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw",
|
||||
"url": "https://youtu.be/eyvLwK5C2dw"
|
||||
}
|
||||
]
|
44
golang/introduction/part-2.json/videos/videos.go
Normal file
44
golang/introduction/part-2.json/videos/videos.go
Normal file
@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type video struct {
|
||||
Id string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Imageurl string `json:"imageurl"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
func getVideos()(videos []video){
|
||||
|
||||
fileBytes, err := ioutil.ReadFile("./videos.json")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(fileBytes, &videos)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return videos
|
||||
}
|
||||
|
||||
func saveVideos(videos []video){
|
||||
|
||||
videoBytes, err := json.Marshal(videos)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile("./videos-updated.json", videoBytes, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
16
golang/introduction/part-2.json/videos/videos.json
Normal file
16
golang/introduction/part-2.json/videos/videos.json
Normal file
@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"id" : "QThadS3Soig",
|
||||
"title" : "Kubernetes on Amazon",
|
||||
"imageurl" : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg",
|
||||
"url" : "https://youtu.be/QThadS3Soig",
|
||||
"description" : ""
|
||||
},
|
||||
{
|
||||
"id" : "eyvLwK5C2dw",
|
||||
"title" : "Kubernetes on Azure",
|
||||
"imageurl" : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw",
|
||||
"url" : "https://youtu.be/eyvLwK5C2dw",
|
||||
"description" : ""
|
||||
}
|
||||
]
|
14
golang/introduction/part-3.http/dockerfile
Normal file
14
golang/introduction/part-3.http/dockerfile
Normal file
@ -0,0 +1,14 @@
|
||||
FROM golang:1.15-alpine as dev
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
FROM golang:1.15-alpine as build
|
||||
|
||||
WORKDIR /videos
|
||||
COPY ./videos/* /videos/
|
||||
RUN go build -o videos
|
||||
|
||||
FROM alpine as runtime
|
||||
COPY --from=build /videos/videos /
|
||||
COPY ./videos/videos.json /
|
||||
CMD ./videos
|
435
golang/introduction/part-3.http/readme.md
Normal file
435
golang/introduction/part-3.http/readme.md
Normal file
@ -0,0 +1,435 @@
|
||||
# Introduction to Go: HTTP
|
||||
|
||||
<a href="https://youtu.be/MKkokYpGyTU" title="golang-part-3"><img src="https://i.ytimg.com/vi/MKkokYpGyTU/hqdefault.jpg" width="20%" alt="introduction to Go part 3" /></a>
|
||||
|
||||
HTTP is a fundamental part of Microservices and Web distributed systems <br/>
|
||||
|
||||
Go has a built in HTTP web server package. The package can be found [here](https://golang.org/pkg/net/http/) <br/>
|
||||
We simply have to import the `http` package:
|
||||
|
||||
```
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
```
|
||||
|
||||
[In part 1](../readme.md), we covered the fundamentals of writing basic Go <br/>
|
||||
[In part 2](../part-2.json/readme.md), we've learn how to use basic data structures like `json` so we can send\receive data. <br/>
|
||||
|
||||
We'll be combining both these techniques so we can serve our `videos` data over a web endpoint.
|
||||
|
||||
As always, let's start with our `dockerfile` , `main.go` and `videos.go` we created in Part 2
|
||||
|
||||
|
||||
## Dev Environment
|
||||
|
||||
The same as Part 1+2, we start with a [dockerfile](./dockerfile) where we declare our version of `go`.
|
||||
|
||||
```
|
||||
cd golang\introduction\part-3.http
|
||||
|
||||
docker build --target dev . -t go
|
||||
docker run -it -v ${PWD}:/work go sh
|
||||
go version
|
||||
```
|
||||
|
||||
## Create our App
|
||||
|
||||
Create a new directory that holds defines our `repository` and holds our `module`
|
||||
|
||||
```
|
||||
mkdir videos
|
||||
|
||||
```
|
||||
|
||||
* Define a module path
|
||||
|
||||
```
|
||||
# change directory to your application source code
|
||||
|
||||
cd videos
|
||||
|
||||
# create a go module file
|
||||
|
||||
go mod init videos
|
||||
|
||||
```
|
||||
|
||||
## Create our base code
|
||||
|
||||
We start out all our applications with a `main.go` defining our `package`, declaring our `import` dependencies <br/>
|
||||
and our entrypoint `main()` function
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, world.")
|
||||
}
|
||||
```
|
||||
|
||||
## Create our Videos app
|
||||
|
||||
Firstly, we create a seperate code file `videos.go` that deals with our YouTube videos <br/>
|
||||
|
||||
The `videos.go` file defines what a video `struct` looks like, a `getVideos()` function to retrieve <br/>
|
||||
videos list as a slice and a `saveVideos()` function to save videos to a file locally. <br/>
|
||||
|
||||
Let's copy the following content from Part 2 and create `videos.go` :
|
||||
|
||||
We want `videos.go` to be part of package main:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
```
|
||||
|
||||
We import 2 packages, 1 for reading and writing files, and another for dealing with `json`
|
||||
|
||||
```
|
||||
import (
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
Then we define what a video `struct` looks like:
|
||||
|
||||
```
|
||||
|
||||
type video struct {
|
||||
Id string
|
||||
Title string
|
||||
Description string
|
||||
Imageurl string
|
||||
Url string
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
We have a function for retrieving `video` objects as a list of type `slice` :
|
||||
|
||||
```
|
||||
|
||||
func getVideos()(videos []video){
|
||||
|
||||
fileBytes, err := ioutil.ReadFile("./videos.json")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(fileBytes, &videos)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return videos
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
We also need to copy our `videos.json` file which contains our video data. <br/>
|
||||
|
||||
And finally, we have a function that accepts a list of type `slice` and stores the videos to a local file
|
||||
|
||||
```
|
||||
func saveVideos(videos []video)(){
|
||||
|
||||
videoBytes, err := json.Marshal(videos)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile("./videos-updated.json", videoBytes, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## HTTP Package
|
||||
|
||||
https://golang.org/pkg/net/http/
|
||||
|
||||
The HTTP package allows us to implement an HTTP client and a server.
|
||||
A client is a component that makes HTTP calls.
|
||||
A server is a component that receives or serves HTTP.
|
||||
|
||||
The HTTP package is capable of sending HTTP requests as well as defining a server
|
||||
for receiving HTTP requests.
|
||||
|
||||
We can use this to run an HTTP server to serve files, or serve data, like an API.
|
||||
|
||||
Let's define a server in `main.go` :
|
||||
|
||||
```
|
||||
# just one line :)
|
||||
|
||||
http.ListenAndServe(":8080", nil)
|
||||
|
||||
# ListenAndServe starts an HTTP server with a given address and handler.
|
||||
# The handler is usually nil, which means to use DefaultServeMux.
|
||||
# Handle and HandleFunc add handlers to DefaultServeMux
|
||||
|
||||
```
|
||||
|
||||
Now before we run this, since we're running in Docker, we want to exit the container <br/>
|
||||
and rerun it, but this time open port `8080`
|
||||
|
||||
```
|
||||
docker run -it -p 8080:8080 -v ${PWD}:/work go sh
|
||||
cd videos
|
||||
|
||||
go run main.go
|
||||
# you will notice the application pausing
|
||||
```
|
||||
|
||||
We should see our server with a 404 on http://localhost:8080/
|
||||
|
||||
## Handle HTTP requests
|
||||
|
||||
In order to handle requests, we can tell the HTTP service that we want it to run a function </br>
|
||||
for the request coming in.
|
||||
|
||||
We can see the `http` package has a `HandleFunc` function: https://golang.org/pkg/net/http/
|
||||
|
||||
To see this in action, lets create a `Hello()` function:
|
||||
|
||||
```
|
||||
func Hello(){
|
||||
}
|
||||
```
|
||||
|
||||
And tell our `http` service to run it:
|
||||
|
||||
```
|
||||
http.HandleFunc("/", Hello)
|
||||
```
|
||||
|
||||
We cannot run this yet. As per `http` documentation, our `Hello` function needs to take in some inputs.
|
||||
`func HandleFunc(pattern string, handler func(ResponseWriter, *Request))`
|
||||
|
||||
Therefore we need to add inputs to our function:
|
||||
|
||||
```
|
||||
func Hello(w http.ResponseWriter, r *http.Request){
|
||||
}
|
||||
```
|
||||
|
||||
This allows us to get the request, its `body`, `headers` and a write where we can send a response.
|
||||
Run this in the browser and you will notice the 404 goes away, but we now get an empty response.
|
||||
|
||||
## HTTP Response
|
||||
|
||||
Let's write a reponse to the incoming request. </br>
|
||||
The response write has a `Write()` function that takes a bunch of bytes. <br/>
|
||||
We can convert string to bytes by casting a `string` to a `[]byte` <br/> like:
|
||||
`[]byte("Hello!")`. Let's convert it and write "Hello" to the response:
|
||||
|
||||
```
|
||||
w.Write([]byte("Hello!"))
|
||||
```
|
||||
|
||||
IF we run this code, we can see "Hello!" in the response body
|
||||
|
||||
## HTTP Headers
|
||||
|
||||
Headers play an important role in HTTP communication. </br>
|
||||
Lets access all the headers of the incoming request!
|
||||
If we look at the Header definition [here](https://golang.org/pkg/net/http), we can see how to access it.
|
||||
Let's use the `for` loop we learnt in [part 1](../readme.md)
|
||||
|
||||
```
|
||||
for i, value := range r.Header {
|
||||
}
|
||||
```
|
||||
|
||||
We learn't from our loop, we have in indexer and a value.
|
||||
For `i`, we can rename it to header since it represents the header key in the dictionary.
|
||||
And the `value` is the value of type `[]string`, containing the value of the header:
|
||||
|
||||
```
|
||||
for header, value := range r.Header {
|
||||
fmt.Printf("Key: %v \t Value: %v \n", header, value)
|
||||
}
|
||||
```
|
||||
|
||||
We can use `fmt` to print out the values and look at the headers.
|
||||
|
||||
We can also set headers on our response. <br/>
|
||||
If we take a look at the `http` docs, we can see header is also a dictionary or strings.
|
||||
|
||||
```
|
||||
w.Header().Add("TestHeader", "TestValue")
|
||||
```
|
||||
|
||||
You can now see the headers in the response value if you use `curl` or your browser development tools
|
||||
|
||||
## HTTP Methods | GET
|
||||
|
||||
Web servers can serve data in a number of ways and support multiple type of HTTP methods.
|
||||
`GET` is used to request data from a specified resource.
|
||||
So far, our HTTP route for our Hello function is using the `GET` method.
|
||||
|
||||
Let's make our `GET` method more useful by serving our video data </br>
|
||||
Let's rename our `Hello()` function to `HandleGetVideos()`. </br>
|
||||
Our `/` route will return all videos:
|
||||
|
||||
```
|
||||
videos := getVideos()
|
||||
```
|
||||
|
||||
In [part 2](../part-2.json/readme.md) we covered `JSON`.
|
||||
We need to convert our video `slice` of `struct`'s to `JSON` in order to return it to the client.
|
||||
|
||||
For this we learnt about the Marshall function:
|
||||
|
||||
Import the `JSON` package:
|
||||
```
|
||||
"encoding/json"
|
||||
```
|
||||
|
||||
Convert our videos to `JSON` :
|
||||
|
||||
```
|
||||
videoBytes, err := json.Marshal(videos)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w.Write(videoBytes)
|
||||
|
||||
```
|
||||
|
||||
If we run this code and hit our `/` endpoint, we can now see `JSON` data being returned. <br/>
|
||||
This is a core part of building an API in Go. <br/>
|
||||
|
||||
## HTTP Methods | POST
|
||||
|
||||
A `POST` method is used to send data to a server to create/update a resource. <br/>
|
||||
Since we built a `saveVideos` function, lets use that so a client can update videos! <br/>
|
||||
|
||||
We need to define a new route, we can all it `/update` :
|
||||
|
||||
```
|
||||
http.HandleFunc("/update", HandleUpdateVideos)
|
||||
```
|
||||
|
||||
And we need to define an `HandleUpdateVideos()` function:
|
||||
|
||||
```
|
||||
func HandleUpdateVideos(w http.ResponseWriter, r *http.Request){
|
||||
}
|
||||
```
|
||||
|
||||
Let's validate the request method to ensure its `POST`
|
||||
We need to also ensure we send a status code to inform the user of method not allowed.
|
||||
|
||||
https://golang.org/pkg/net/http/#ResponseWriter
|
||||
|
||||
```
|
||||
if r.Method == "POST" {
|
||||
//update our videos here!
|
||||
} else {
|
||||
w.WriteHeader(405)
|
||||
fmt.Fprintf(w, "Method not Supported!")
|
||||
}
|
||||
```
|
||||
|
||||
Now we need to accept `JSON` from the `POST` request body
|
||||
https://golang.org/pkg/net/http/#Request
|
||||
|
||||
In the docs above, we can see the request Body is of type `Body io.ReadCloser`
|
||||
To read that, we can use the `ioutil` package
|
||||
https://golang.org/pkg/io/ioutil/
|
||||
|
||||
```
|
||||
import "io/ioutil"
|
||||
```
|
||||
|
||||
Then we can read the body into a `slice` of `bytes`:
|
||||
|
||||
```
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
```
|
||||
|
||||
Now that we have the body in a `[]byte`, we need to use our knowledge from [part 2](../part-2.json/readme.md) where we <br/>
|
||||
convert `[]byte` to a `slice` of `video` items.
|
||||
|
||||
```
|
||||
|
||||
var videos []video
|
||||
err = json.Unmarshal(body, &videos)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
```
|
||||
|
||||
Creating our video objects allows us to do some validation if we wanted to. <br/>
|
||||
We can ensure the request body adheres to our API contract for this videos API. <br/>
|
||||
So instead of calling `panic`, lets return a `400` Bad request status code if we cannot <br/>
|
||||
Unmarshal the `JSON` data. This might help with some basic validation.
|
||||
|
||||
```
|
||||
w.WriteHeader(400)
|
||||
fmt.Fprintf(w, "Bad request")
|
||||
```
|
||||
|
||||
And Finally, let's update our videos file! :
|
||||
|
||||
```
|
||||
saveVideos(videos)
|
||||
```
|
||||
|
||||
# Build our Docker container
|
||||
|
||||
Let's uncomment all the build lines in the `dockerfile`
|
||||
Full `dockerfile` :
|
||||
|
||||
```
|
||||
FROM golang:1.15-alpine as dev
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
FROM golang:1.15-alpine as build
|
||||
|
||||
WORKDIR /videos
|
||||
COPY ./videos/* /videos/
|
||||
RUN go build -o videos
|
||||
|
||||
|
||||
FROM alpine as runtime
|
||||
COPY --from=build /videos/videos /
|
||||
COPY ./videos/videos.json /
|
||||
CMD ./videos
|
||||
|
||||
```
|
||||
|
||||
Build :
|
||||
```
|
||||
cd golang\introduction\part-3.http
|
||||
docker build . -t videos
|
||||
```
|
||||
|
||||
Run :
|
||||
```
|
||||
docker run -it -p 8080:8080 videos
|
||||
```
|
||||
|
||||
## Things to know
|
||||
|
||||
* SSL for secure web connection
|
||||
* Authentication
|
||||
* Good API validation
|
||||
* Support a backwards compatible contract (Inputs remain consistent)
|
3
golang/introduction/part-3.http/videos/go.mod
Normal file
3
golang/introduction/part-3.http/videos/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module videos
|
||||
|
||||
go 1.15
|
54
golang/introduction/part-3.http/videos/main.go
Normal file
54
golang/introduction/part-3.http/videos/main.go
Normal file
@ -0,0 +1,54 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
http.HandleFunc("/", HandleGetVideos)
|
||||
http.HandleFunc("/update", HandleUpdateVideos)
|
||||
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
|
||||
|
||||
func HandleGetVideos(w http.ResponseWriter, r *http.Request){
|
||||
|
||||
videos := getVideos()
|
||||
|
||||
videoBytes, err := json.Marshal(videos)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w.Write(videoBytes)
|
||||
}
|
||||
|
||||
func HandleUpdateVideos(w http.ResponseWriter, r *http.Request){
|
||||
|
||||
if r.Method == "POST" {
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var videos []video
|
||||
err = json.Unmarshal(body, &videos)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
fmt.Fprintf(w, "Bad request")
|
||||
}
|
||||
|
||||
saveVideos(videos)
|
||||
|
||||
} else {
|
||||
w.WriteHeader(405)
|
||||
fmt.Fprintf(w, "Method not Supported!")
|
||||
}
|
||||
}
|
16
golang/introduction/part-3.http/videos/videos-updated.json
Normal file
16
golang/introduction/part-3.http/videos/videos-updated.json
Normal file
@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"Id": "QThadS3Soig",
|
||||
"Title": "Kubernetes on Amazon",
|
||||
"Description": "TEST",
|
||||
"Imageurl": "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg",
|
||||
"Url": "https://youtu.be/QThadS3Soig"
|
||||
},
|
||||
{
|
||||
"Id": "eyvLwK5C2dw",
|
||||
"Title": "Kubernetes on Azure",
|
||||
"Description": "TEST",
|
||||
"Imageurl": "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF\u0026rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw",
|
||||
"Url": "https://youtu.be/eyvLwK5C2dw"
|
||||
}
|
||||
]
|
46
golang/introduction/part-3.http/videos/videos.go
Normal file
46
golang/introduction/part-3.http/videos/videos.go
Normal file
@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type video struct {
|
||||
Id string
|
||||
Title string
|
||||
Description string
|
||||
Imageurl string
|
||||
Url string
|
||||
}
|
||||
|
||||
|
||||
func getVideos()(videos []video){
|
||||
|
||||
fileBytes, err := ioutil.ReadFile("./videos.json")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(fileBytes, &videos)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return videos
|
||||
}
|
||||
|
||||
func saveVideos(videos []video)(){
|
||||
|
||||
videoBytes, err := json.Marshal(videos)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile("./videos-updated.json", videoBytes, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
16
golang/introduction/part-3.http/videos/videos.json
Normal file
16
golang/introduction/part-3.http/videos/videos.json
Normal file
@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"id" : "QThadS3Soig",
|
||||
"title" : "Kubernetes on Amazon",
|
||||
"imageurl" : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg",
|
||||
"url" : "https://youtu.be/QThadS3Soig",
|
||||
"description" : ""
|
||||
},
|
||||
{
|
||||
"id" : "eyvLwK5C2dw",
|
||||
"title" : "Kubernetes on Azure",
|
||||
"imageurl" : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw",
|
||||
"url" : "https://youtu.be/eyvLwK5C2dw",
|
||||
"description" : ""
|
||||
}
|
||||
]
|
17
golang/introduction/part-4.commandline/dockerfile
Normal file
17
golang/introduction/part-4.commandline/dockerfile
Normal file
@ -0,0 +1,17 @@
|
||||
FROM golang:1.15-alpine as dev
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
# FROM golang:1.15-alpine as build
|
||||
|
||||
# WORKDIR /videos
|
||||
# COPY ./videos/* /videos/
|
||||
# RUN go build -o videos
|
||||
|
||||
|
||||
# FROM alpine as runtime
|
||||
# COPY --from=build /videos/videos /usr/local/bin/videos
|
||||
# COPY ./videos/videos.json /
|
||||
# COPY run.sh /
|
||||
# RUN chmod +x /run.sh
|
||||
# ENTRYPOINT [ "./run.sh" ]
|
418
golang/introduction/part-4.commandline/readme.md
Normal file
418
golang/introduction/part-4.commandline/readme.md
Normal file
@ -0,0 +1,418 @@
|
||||
# Introduction to Go: Command Line
|
||||
|
||||
<a href="https://youtu.be/CODqM_rzwtk" title="golang-part-4"><img src="https://i.ytimg.com/vi/CODqM_rzwtk/hqdefault.jpg" width="20%" alt="introduction to Go part 4" /></a>
|
||||
|
||||
Command line apps are a fundamental part of software development <br/>
|
||||
|
||||
Go has a built in Commandline parser package. The package can be found [here](https://golang.org/pkg/flag/) <br/>
|
||||
We simply have to import the `flag` package:
|
||||
|
||||
```
|
||||
import (
|
||||
"flag"
|
||||
)
|
||||
```
|
||||
|
||||
[In part 1](../readme.md), we covered the fundamentals of writing basic Go <br/>
|
||||
[In part 2](../part-2.json/readme.md), we've learn how to use basic data structures like `json` so we can send\receive data. <br/>
|
||||
[Part 3](../part-3.http/readme.md) was about exposing data via a Web server.
|
||||
|
||||
We'll be combining these techniques so we can serve our `videos` data over a commandline application.
|
||||
|
||||
As always, let's start with our `dockerfile` , `main.go` and `videos.go` we created in Part 2
|
||||
|
||||
## Dev Environment
|
||||
|
||||
The same as Part 1+2+3, we start with a [dockerfile](./dockerfile) where we declare our version of `go`.
|
||||
|
||||
```
|
||||
cd golang\introduction\part-4.commandline
|
||||
|
||||
docker build --target dev . -t go
|
||||
docker run -it -v ${PWD}:/work go sh
|
||||
go version
|
||||
```
|
||||
|
||||
## Create our App
|
||||
|
||||
Create a new directory that holds defines our `repository` and holds our `module`
|
||||
|
||||
```
|
||||
mkdir videos
|
||||
cd videos
|
||||
```
|
||||
|
||||
* Define a module path
|
||||
|
||||
```
|
||||
# create a go module file
|
||||
|
||||
go mod init videos
|
||||
|
||||
```
|
||||
|
||||
## Create our base code
|
||||
|
||||
We start out all our applications with a `main.go` defining our `package`, declaring our `import` dependencies <br/>
|
||||
and our entrypoint `main()` function
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, world.")
|
||||
}
|
||||
```
|
||||
|
||||
## Create our Videos app
|
||||
|
||||
Firstly, we create a seperate code file `videos.go` that deals with our YouTube videos <br/>
|
||||
|
||||
The `videos.go` file defines what a video `struct` looks like, a `getVideos()` function to retrieve <br/>
|
||||
videos list as a slice and a `saveVideos()` function to save videos to a file locally. <br/>
|
||||
|
||||
Let's copy the following content from Part 2 and create `videos.go` :
|
||||
|
||||
We want `videos.go` to be part of package main:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
```
|
||||
|
||||
We import 2 packages, 1 for reading and writing files, and another for dealing with `json`
|
||||
|
||||
```
|
||||
import (
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
Then we define what a video `struct` looks like:
|
||||
|
||||
```
|
||||
|
||||
type video struct {
|
||||
Id string
|
||||
Title string
|
||||
Description string
|
||||
Imageurl string
|
||||
Url string
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
We have a function for retrieving `video` objects as a list of type `slice` :
|
||||
|
||||
```
|
||||
|
||||
func getVideos()(videos []video){
|
||||
|
||||
fileBytes, err := ioutil.ReadFile("./videos.json")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(fileBytes, &videos)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return videos
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
We also need to copy our `videos.json` file which contains our video data. <br/>
|
||||
|
||||
And finally, we have a function that accepts a list of type `slice` and stores the videos to a local file
|
||||
|
||||
```
|
||||
func saveVideos(videos []video)(){
|
||||
|
||||
videoBytes, err := json.Marshal(videos)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile("./videos.json", videoBytes, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## Flag Package
|
||||
|
||||
https://golang.org/pkg/flag/
|
||||
|
||||
Package flag implements command-line flag parsing.
|
||||
So we can run our videos app and pass it inputs like any other CLI.
|
||||
|
||||
Let's build a CLI tool that users our `getVideos()` and `saveVideos` functions.
|
||||
|
||||
To get all videos, perhaps we'd like a command
|
||||
```
|
||||
# get all videos
|
||||
videos get --all
|
||||
|
||||
# get video by ID
|
||||
videos get -id <video-id>
|
||||
|
||||
# add a video to our list
|
||||
videos add -id -title -url -imageurl -desc
|
||||
|
||||
|
||||
```
|
||||
To start, we import package `flag`
|
||||
|
||||
```
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
Let's define our subcommands in `main.go` :
|
||||
|
||||
```
|
||||
//'videos get' subcommand
|
||||
getCmd := flag.NewFlagSet("get", flag.ExitOnError)
|
||||
```
|
||||
|
||||
`videos get` command will need two inputs, `--all` if the user wants to return all videos, or `--id` if the user only wants a specific video
|
||||
|
||||
```
|
||||
// inputs for `videos get` command
|
||||
getAll := getCmd.Bool("all", false, "Get all videos")
|
||||
getID := getCmd.String("id", "", "YouTube video ID")
|
||||
```
|
||||
|
||||
`videos add` command will take a bit more inputs to be able to add a video to our storage.
|
||||
```
|
||||
addCmd := flag.NewFlagSet("add", flag.ExitOnError)
|
||||
|
||||
addID := addCmd.String("id", "", "YouTube video ID")
|
||||
addTitle := addCmd.String("title", "", "YouTube video Title")
|
||||
addUrl := addCmd.String("url", "", "YouTube video URL")
|
||||
addImageUrl := addCmd.String("imageurl", "", "YouTube video Image URL")
|
||||
addDesc := addCmd.String("desc", "", "YouTube video description")
|
||||
|
||||
|
||||
```
|
||||
|
||||
When a user runs our videos CLI tool, we may need to validate that
|
||||
our application receives the right subcommands. So lets ensure a simple validation to check if the user has passed a subcommand
|
||||
|
||||
To check the arguments passed to our CLI, we use the ["os"](https://golang.org/pkg/os/) package. Check the Args variable, it holds usefull information passed to our application including its name.
|
||||
`var Args []string`
|
||||
|
||||
We can do a simple check by ensuring the length of `os.Args` is atleast 2.
|
||||
|
||||
We firstly need to add `os` to our import section </br>
|
||||
Followed by our check:
|
||||
|
||||
```
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("expected 'get' or 'add' subcommands")
|
||||
os.Exit(1)
|
||||
}
|
||||
```
|
||||
|
||||
## Handling our subcommands
|
||||
|
||||
So to handle each sub command like `get` and `add`, we add a simple
|
||||
`switch` statement that can branch into different pathways of execution,
|
||||
based on a variables content.
|
||||
|
||||
Let's take a look at this simple `switch` statement:
|
||||
|
||||
```
|
||||
//look at the 2nd argument's value
|
||||
switch os.Args[1] {
|
||||
case "get": // if its the 'get' command
|
||||
//hande get here
|
||||
case "add": // if its the 'add' command
|
||||
//hande add here
|
||||
default: // if we don't understand the input
|
||||
}
|
||||
```
|
||||
|
||||
Let's create seperate handler functions for each sub command to keep our code tidy:
|
||||
|
||||
```
|
||||
func HandleGet(getCmd *flag.FlagSet, all *bool, id *string){
|
||||
}
|
||||
|
||||
func HandleAdd(addCmd *flag.FlagSet,id *string, title *string, url *string, imageUrl *string, description *string ){
|
||||
}
|
||||
```
|
||||
|
||||
Now that we have seperate functions for each subcommand, we can take appropriate actions in each. Let's firstly parse the command flags for each subcommand:
|
||||
|
||||
This allows us to parse everything after the `videos <subcommand>` arguments:
|
||||
|
||||
```
|
||||
getCmd.Parse(os.Args[2:])
|
||||
```
|
||||
|
||||
## Input Validation
|
||||
|
||||
For our `HandleGet` function, let's validate input to ensure its correct.
|
||||
|
||||
```
|
||||
if *all == false && *id == "" {
|
||||
fmt.Print("id is required or specify --all for all videos")
|
||||
getCmd.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
```
|
||||
|
||||
Let's handle the scenario if user passing `--all` flag:
|
||||
|
||||
```
|
||||
if *all {
|
||||
//return all videos
|
||||
videos := getVideos()
|
||||
|
||||
fmt.Printf("ID \t Title \t URL \t ImageURL \t Description \n")
|
||||
for _, video := range videos {
|
||||
fmt.Printf("%v \t %v \t %v \t %v \t %v \n",video.Id, video.Title, video.Url, video.Imageurl,video.Description)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
Let's handle when user is searching for a video by ID
|
||||
|
||||
```
|
||||
if *id != "" {
|
||||
videos := getVideos()
|
||||
id := *id
|
||||
for _, video := range videos {
|
||||
if id == video.Id {
|
||||
fmt.Printf("ID \t Title \t URL \t ImageURL \t Description \n")
|
||||
fmt.Printf("%v \t %v \t %v \t %v \t %v \n",video.Id, video.Title, video.Url, video.Imageurl,video.Description)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Parsing multiple fields
|
||||
|
||||
For our `HandleAdd` function, we need to validate multiple inputs, create a `video` struct, append it to the existing video list and save it back to file
|
||||
|
||||
Let's create a `ValidateVideo()` function with similar inputs to our `HandleAdd()`:
|
||||
|
||||
```
|
||||
func ValidateVideo(addCmd *flag.FlagSet,id *string, title *string, url *string, imageUrl *string, description *string ){
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Let's simply validate all fields since they are all required:
|
||||
|
||||
```
|
||||
if *id == "" || *title == "" || *url == "" || *imageUrl == "" || *description == "" {
|
||||
fmt.Print("all fields are required for adding a video")
|
||||
addCmd.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
```
|
||||
|
||||
And we can now call this function in our add function:
|
||||
|
||||
```
|
||||
ValidateVideo(addCmd, id,title,url, imageUrl, description)
|
||||
```
|
||||
|
||||
## Adding our video
|
||||
|
||||
Now that we have some basic validation, not perfect, but good enough to get started, let's add our video to the existing file.
|
||||
|
||||
Define a video struct with the CLI input:
|
||||
|
||||
```
|
||||
video := video{
|
||||
Id: *id,
|
||||
Title: *title,
|
||||
Description: *description,
|
||||
Imageurl: *imageUrl,
|
||||
Url: *url,
|
||||
}
|
||||
```
|
||||
|
||||
Get the existing videos:
|
||||
|
||||
```
|
||||
videos := getVideos()
|
||||
```
|
||||
|
||||
Append our video to the list:
|
||||
|
||||
```
|
||||
videos = append(videos,video)
|
||||
```
|
||||
|
||||
Save the new updated video list:
|
||||
|
||||
```
|
||||
saveVideos(videos)
|
||||
```
|
||||
|
||||
# Build our Docker container
|
||||
|
||||
Let's uncomment all the build lines in the `dockerfile`
|
||||
Full `dockerfile` :
|
||||
|
||||
```
|
||||
FROM golang:1.15-alpine as dev
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
FROM golang:1.15-alpine as build
|
||||
|
||||
WORKDIR /videos
|
||||
COPY ./videos/* /videos/
|
||||
RUN go build -o videos
|
||||
|
||||
|
||||
FROM alpine as runtime
|
||||
COPY --from=build /videos/videos /usr/local/bin/videos
|
||||
COPY ./videos/videos.json /
|
||||
COPY run.sh /
|
||||
RUN chmod +x /run.sh
|
||||
ENTRYPOINT [ "./run.sh" ]
|
||||
```
|
||||
|
||||
For our entrypoint, we need to create a shell script to accept all the arguments:
|
||||
|
||||
Let's create a script called `run.sh`
|
||||
|
||||
```
|
||||
#!/bin/sh
|
||||
|
||||
videos $@
|
||||
|
||||
```
|
||||
|
||||
Build :
|
||||
```
|
||||
cd golang\introduction\part-4.commandline
|
||||
docker build . -t videos
|
||||
```
|
||||
|
||||
Run :
|
||||
```
|
||||
docker run -it videos get --help
|
||||
```
|
3
golang/introduction/part-4.commandline/run.sh
Normal file
3
golang/introduction/part-4.commandline/run.sh
Normal file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
videos $@
|
3
golang/introduction/part-4.commandline/videos/go.mod
Normal file
3
golang/introduction/part-4.commandline/videos/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module videos
|
||||
|
||||
go 1.15
|
111
golang/introduction/part-4.commandline/videos/main.go
Normal file
111
golang/introduction/part-4.commandline/videos/main.go
Normal file
@ -0,0 +1,111 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
//'videos get' subcommand
|
||||
getCmd := flag.NewFlagSet("get", flag.ExitOnError)
|
||||
|
||||
// inputs for `videos get` command
|
||||
getAll := getCmd.Bool("all", false, "Get all videos")
|
||||
getID := getCmd.String("id", "", "YouTube video ID")
|
||||
|
||||
//'videos add' subcommand
|
||||
addCmd := flag.NewFlagSet("add", flag.ExitOnError)
|
||||
|
||||
// inputs for `videos add` command
|
||||
addID := addCmd.String("id", "", "YouTube video ID")
|
||||
addTitle := addCmd.String("title", "", "YouTube video Title")
|
||||
addUrl := addCmd.String("url", "", "YouTube video URL")
|
||||
addImageUrl := addCmd.String("imageurl", "", "YouTube video Image URL")
|
||||
addDesc := addCmd.String("desc", "", "YouTube video description")
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("expected 'get' or 'add' subcommands")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
//look at the 2nd argument's value
|
||||
switch os.Args[1] {
|
||||
case "get": // if its the 'get' command
|
||||
HandleGet(getCmd, getAll, getID)
|
||||
case "add": // if its the 'add' command
|
||||
HandleAdd(addCmd, addID,addTitle,addUrl, addImageUrl, addDesc)
|
||||
default: // if we don't understand the input
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
func HandleGet(getCmd *flag.FlagSet, all *bool, id *string){
|
||||
|
||||
getCmd.Parse(os.Args[2:])
|
||||
|
||||
if *all == false && *id == "" {
|
||||
fmt.Print("id is required or specify --all for all videos")
|
||||
getCmd.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if *all {
|
||||
//return all videos
|
||||
videos := getVideos()
|
||||
|
||||
fmt.Printf("ID \t Title \t URL \t ImageURL \t Description \n")
|
||||
for _, video := range videos {
|
||||
fmt.Printf("%v \t %v \t %v \t %v \t %v \n",video.Id, video.Title, video.Url, video.Imageurl,video.Description)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if *id != "" {
|
||||
videos := getVideos()
|
||||
id := *id
|
||||
for _, video := range videos {
|
||||
if id == video.Id {
|
||||
fmt.Printf("ID \t Title \t URL \t ImageURL \t Description \n")
|
||||
fmt.Printf("%v \t %v \t %v \t %v \t %v \n",video.Id, video.Title, video.Url, video.Imageurl,video.Description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
func ValidateVideo(addCmd *flag.FlagSet,id *string, title *string, url *string, imageUrl *string, description *string ){
|
||||
|
||||
addCmd.Parse(os.Args[2:])
|
||||
|
||||
if *id == "" || *title == "" || *url == "" || *imageUrl == "" || *description == "" {
|
||||
fmt.Print("all fields are required for adding a video")
|
||||
addCmd.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func HandleAdd(addCmd *flag.FlagSet,id *string, title *string, url *string, imageUrl *string, description *string ){
|
||||
|
||||
ValidateVideo(addCmd, id,title,url, imageUrl, description)
|
||||
|
||||
video := video{
|
||||
Id: *id,
|
||||
Title: *title,
|
||||
Description: *description,
|
||||
Imageurl: *imageUrl,
|
||||
Url: *url,
|
||||
}
|
||||
|
||||
videos := getVideos()
|
||||
videos = append(videos,video)
|
||||
|
||||
saveVideos(videos)
|
||||
|
||||
}
|
BIN
golang/introduction/part-4.commandline/videos/videos
Normal file
BIN
golang/introduction/part-4.commandline/videos/videos
Normal file
Binary file not shown.
45
golang/introduction/part-4.commandline/videos/videos.go
Normal file
45
golang/introduction/part-4.commandline/videos/videos.go
Normal file
@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type video struct {
|
||||
Id string
|
||||
Title string
|
||||
Description string
|
||||
Imageurl string
|
||||
Url string
|
||||
}
|
||||
|
||||
func getVideos()(videos []video){
|
||||
|
||||
fileBytes, err := ioutil.ReadFile("./videos.json")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(fileBytes, &videos)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return videos
|
||||
}
|
||||
|
||||
func saveVideos(videos []video)(){
|
||||
|
||||
videoBytes, err := json.Marshal(videos)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile("./videos.json", videoBytes, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
16
golang/introduction/part-4.commandline/videos/videos.json
Normal file
16
golang/introduction/part-4.commandline/videos/videos.json
Normal file
@ -0,0 +1,16 @@
|
||||
[
|
||||
{
|
||||
"Id": "QThadS3Soig",
|
||||
"Title": "Kubernetes on Amazon",
|
||||
"Description": "",
|
||||
"Imageurl": "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg",
|
||||
"Url": "https://youtu.be/QThadS3Soig"
|
||||
},
|
||||
{
|
||||
"Id": "eyvLwK5C2dw",
|
||||
"Title": "Kubernetes on Azure",
|
||||
"Description": "",
|
||||
"Imageurl": "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF\u0026rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw",
|
||||
"Url": "https://youtu.be/eyvLwK5C2dw"
|
||||
}
|
||||
]
|
13
golang/introduction/part-5.database.redis/dockerfile
Normal file
13
golang/introduction/part-5.database.redis/dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM golang:1.15-alpine as dev
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
FROM golang:1.15-alpine as build
|
||||
|
||||
WORKDIR /videos
|
||||
COPY ./videos/* /videos/
|
||||
RUN go build -o videos
|
||||
|
||||
FROM alpine as runtime
|
||||
COPY --from=build /videos/videos /
|
||||
CMD ./videos
|
559
golang/introduction/part-5.database.redis/readme.md
Normal file
559
golang/introduction/part-5.database.redis/readme.md
Normal file
@ -0,0 +1,559 @@
|
||||
# Introduction to Go: Storing data in Redis Database
|
||||
|
||||
<a href="https://youtu.be/6lJCyKwoQaQ" title="golang-part-5"><img src="https://i.ytimg.com/vi/6lJCyKwoQaQ/hqdefault.jpg" width="20%" alt="introduction to Go part 5" /></a>
|
||||
|
||||
Up until now, we've learned the fundamentals of Go and built a small web microservice that handles our video data.
|
||||
Our service has a `/` `GET` endpoint for returning all videos, as well as a simple `/update` endpoint for updating our list of videos.
|
||||
|
||||
We've learnt how to read and write from files and learn how to work with `json` data. </br>
|
||||
This is important for learning Go, however there are a few challenges for using a file as storage. <br/>
|
||||
|
||||
* It can be problematic if we have more than one instance of our service writing to the same file
|
||||
* It's important to keep state out of our application, so if our application crashes, we don't lose our data
|
||||
|
||||
[In part 1](../readme.md), we covered the fundamentals of writing basic Go <br/>
|
||||
[In part 2](../part-2.json/readme.md), we've learnt how to use basic data structures like `json` so we can send\receive data. <br/>
|
||||
[In part 3](../part-3.http/readme.md), we've learnt how to write a HTTP service to expose our videos data <br/>
|
||||
|
||||
## Start up a Redis Cluster
|
||||
|
||||
Follow my Redis clustering Tutorial </br>
|
||||
|
||||
<a href="https://youtube.com/playlist?list=PLHq1uqvAteVtlgFkmOlIqWro3XP26y_oW" title="Redis"><img src="https://i.ytimg.com/vi/L3zp347cWNw/hqdefault.jpg" width="30%" alt="Redis Guide" /></a>
|
||||
|
||||
Code is over [here](../../../storage/redis/clustering/readme.md)
|
||||
|
||||
## Go Dev Environment
|
||||
|
||||
The same as Part 1+2+3+4, we start with a [dockerfile](./dockerfile) where we declare our version of `go`.
|
||||
|
||||
The `dockerfile`:
|
||||
|
||||
```
|
||||
FROM golang:1.15-alpine as dev
|
||||
|
||||
WORKDIR /work
|
||||
```
|
||||
|
||||
Let's build and start our container:
|
||||
|
||||
```
|
||||
cd golang\introduction\part-5.database.redis
|
||||
|
||||
docker build --target dev . -t go
|
||||
docker run -it -p 80:80 -v ${PWD}:/work go sh
|
||||
go version
|
||||
```
|
||||
|
||||
## Our application
|
||||
|
||||
Create a new directory that holds defines our `repository` and holds our `module`
|
||||
|
||||
```
|
||||
mkdir videos
|
||||
|
||||
```
|
||||
|
||||
* Define a module path
|
||||
|
||||
```
|
||||
# change directory to your application source code
|
||||
|
||||
cd videos
|
||||
|
||||
# create a go module file
|
||||
|
||||
go mod init videos
|
||||
|
||||
```
|
||||
|
||||
## Create our base code
|
||||
|
||||
We start out all our applications with a `main.go` defining our `package`, declaring our `import` dependencies <br/>
|
||||
and our entrypoint `main()` function
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
http.HandleFunc("/", HandleGetVideos)
|
||||
http.HandleFunc("/update", HandleUpdateVideos)
|
||||
|
||||
http.ListenAndServe(":80", nil)
|
||||
}
|
||||
```
|
||||
|
||||
Now before we write these handler functions, we need our videos application
|
||||
|
||||
## Create our Videos app
|
||||
|
||||
Firstly, we create a separate code file `videos.go` that deals with our YouTube videos <br/>
|
||||
|
||||
The `videos.go` file defines what a video `struct` looks like, a `getVideos()` function to retrieve <br/>
|
||||
videos list as a slice and a `saveVideos()` function to save videos to a file locally. <br/>
|
||||
|
||||
We start with our dependencies. <br>
|
||||
We import 2 packages, 1 for reading and writing files, and another for dealing with `json`
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
Then we define what a video `struct` looks like:
|
||||
|
||||
```
|
||||
type video struct {
|
||||
Id string
|
||||
Title string
|
||||
Description string
|
||||
Imageurl string
|
||||
Url string
|
||||
}
|
||||
```
|
||||
|
||||
We have a function for retrieving `video` objects as a list of type `slice` :
|
||||
|
||||
```
|
||||
func getVideos()(videos []video){
|
||||
|
||||
fileBytes, err := ioutil.ReadFile("./videos.json")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(fileBytes, &videos)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return videos
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
We also need to copy our `videos.json` file which contains our video data. <br/>
|
||||
|
||||
And finally, we have a function that accepts a list of type `slice` and stores the videos to a local file
|
||||
|
||||
```
|
||||
func saveVideos(videos []video)(){
|
||||
|
||||
videoBytes, err := json.Marshal(videos)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile("./videos.json", videoBytes, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## HTTP Handlers
|
||||
|
||||
Now we have to define our handler functions `HandleGetVideos` and `HandleUpdateVideos`,
|
||||
|
||||
```
|
||||
func HandleGetVideos(w http.ResponseWriter, r *http.Request){
|
||||
|
||||
videos := getVideos()
|
||||
videoBytes, err := json.Marshal(videos)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w.Write(videoBytes)
|
||||
}
|
||||
|
||||
func HandleUpdateVideos(w http.ResponseWriter, r *http.Request){
|
||||
|
||||
if r.Method == "POST" {
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var videos []video
|
||||
err = json.Unmarshal(body, &videos)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
fmt.Fprintf(w, "Bad request")
|
||||
}
|
||||
|
||||
saveVideos(videos)
|
||||
|
||||
} else {
|
||||
w.WriteHeader(405)
|
||||
fmt.Fprintf(w, "Method not Supported!")
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Now so far, we have a web HTTP application that can list and update our youtube videos. <br/>
|
||||
Let's build and run it
|
||||
|
||||
```
|
||||
go build
|
||||
|
||||
./videos
|
||||
|
||||
```
|
||||
|
||||
If we head over to `http://localhost` in the browser we can see our application returns our 2 videos. </br>.
|
||||
We can use tools like PostMan to generate a `POST` request to update our videos too.
|
||||
|
||||
|
||||
## Redis Go Package
|
||||
|
||||
Now instead of reading and writing our records to a `json` file, we are going to read and write records to Redis.
|
||||
|
||||
https://github.com/go-redis/redis/
|
||||
|
||||
```
|
||||
go get github.com/go-redis/redis/v8
|
||||
|
||||
```
|
||||
|
||||
And to use the library, we have to import it
|
||||
|
||||
```
|
||||
import (
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
```
|
||||
|
||||
Now to connect to any database, you're going to need a bit of information:
|
||||
|
||||
* Database Address
|
||||
* Database Port
|
||||
* Database Username\Password
|
||||
|
||||
We can define these as environment variables and read those in our code
|
||||
|
||||
```
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
//main.go (global variables)
|
||||
|
||||
var redis_sentinels = os.Getenv("REDIS_SENTINELS")
|
||||
var redis_master = os.Getenv("REDIS_MASTER_NAME")
|
||||
var redis_password = os.Getenv("REDIS_PASSWORD")
|
||||
|
||||
```
|
||||
|
||||
We define an empty context and Redis client
|
||||
Context can be used to control timeouts and deadlines for our application. We can set up an empty context for now.
|
||||
|
||||
```
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
var redisClient *redis.Client
|
||||
|
||||
```
|
||||
|
||||
## Redis Sentinel Client
|
||||
|
||||
https://redis.uptrace.dev/guide/sentinel.html#redis-server-client
|
||||
|
||||
|
||||
```
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
sentinelAddrs := strings.Split(redis_sentinels, ",")
|
||||
rdb := redis.NewFailoverClient(&redis.FailoverOptions{
|
||||
MasterName: redis_master,
|
||||
SentinelAddrs: sentinelAddrs,
|
||||
Password: redis_password,
|
||||
DB: 0,
|
||||
})
|
||||
|
||||
redisClient = rdb
|
||||
|
||||
rdb.Ping(ctx)
|
||||
```
|
||||
|
||||
We can also add the `Ping()` to our handler functions to ensure it can connect using the global redis client variable
|
||||
|
||||
## Networking
|
||||
|
||||
Now we need to remember our go container may not be able to talk to the redis containers because they are running on different networks.
|
||||
|
||||
If you took note, we started our Redis containers on a `redis` network by passing `--net redis` as a flag to our `docker run` commands. </br>
|
||||
|
||||
Let's restart our Go container on the same network
|
||||
|
||||
We also need to set our `ENV` variables to point our container to the redis sentinels. </br>
|
||||
|
||||
If we look at our sentinel configuration, our master alias is set to `mymaster`
|
||||
|
||||
```
|
||||
docker run -it -p 80:80 `
|
||||
--net redis `
|
||||
-e REDIS_SENTINELS="sentinel-0:5000,sentinel-1:5000,sentinel-2:5000" `
|
||||
-e REDIS_MASTER_NAME="mymaster" `
|
||||
-e REDIS_PASSWORD="a-very-complex-password-here" `
|
||||
-v ${PWD}:/work go sh
|
||||
```
|
||||
|
||||
We can now observe our container is connected to Redis. </br>
|
||||
Our application: [http://localhost](http://localhost)
|
||||
|
||||
# Store our Data
|
||||
|
||||
So now we can store our video data in Redis instead of a local `json` file. </br>
|
||||
We'll write the `json` document of a video (struct) to Redis. </br>
|
||||
|
||||
Let's create a `saveVideo()` function that stores a single record.
|
||||
|
||||
```
|
||||
func saveVideo(video video)(){
|
||||
|
||||
videoBytes, err := json.Marshal(video)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = redisClient.Set(ctx, video.Id, videoBytes, 0).Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now since we have one endpoint that saves all videos, we need to update it to save each video it has.
|
||||
|
||||
```
|
||||
func saveVideos(videos []video)(){
|
||||
for _, video := range videos {
|
||||
saveVideo(video)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To get the videos, let's create a function to retrieve a single record:
|
||||
|
||||
```
|
||||
func getVideo(id string)(video video) {
|
||||
|
||||
value, err := redisClient.Get(ctx, id).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err != redis.Nil {
|
||||
err = json.Unmarshal([]byte(value), &video)
|
||||
}
|
||||
|
||||
return video
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
And finally we need to update our `GetVideos()` function to loop all the video keys and return all videos
|
||||
|
||||
```
|
||||
func getVideos()(videos []video){
|
||||
|
||||
keys, err := redisClient.Keys(ctx,"*").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
video := getVideo(key)
|
||||
videos = append(videos, video)
|
||||
}
|
||||
|
||||
return videos
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
And we need all our imports
|
||||
|
||||
```
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
```
|
||||
|
||||
Now if we rebuild our all and access it, we get `null` as there are no videos in Redis. Let's add two!
|
||||
|
||||
Let's `POST` the following using PostMan to url `http://localhost/update`
|
||||
```
|
||||
[
|
||||
{
|
||||
"id" : "QThadS3Soig",
|
||||
"title" : "Kubernetes on Amazon",
|
||||
"imageurl" : "https://i.ytimg.com/vi/QThadS3Soig/sddefault.jpg",
|
||||
"url" : "https://youtu.be/QThadS3Soig",
|
||||
"description" : "TEST"
|
||||
},
|
||||
{
|
||||
"id" : "eyvLwK5C2dw",
|
||||
"title" : "Kubernetes on Azure",
|
||||
"imageurl" : "https://i.ytimg.com/vi/eyvLwK5C2dw/mqdefault.jpg?sqp=CISC_PoF&rs=AOn4CLDo7kizrJozB0pxBhxL9JbyiW_EPw",
|
||||
"url" : "https://youtu.be/eyvLwK5C2dw",
|
||||
"description" : "TEST"
|
||||
}
|
||||
]
|
||||
|
||||
```
|
||||
|
||||
If we refresh our page, we can now see two records!
|
||||
|
||||
# Improvements
|
||||
|
||||
Now that you have the fundamental knowledge of HTTP and Redis, </br>
|
||||
you can update the code to retrieve 1 video by ID, or delete a video by ID. </br>
|
||||
You can add search functionality and more! </br>
|
||||
|
||||
Let's update our `/ GET` handler to be able to return a single video
|
||||
|
||||
```
|
||||
func HandleGetVideos(w http.ResponseWriter, r *http.Request){
|
||||
|
||||
id, ok := r.URL.Query()["id"]
|
||||
|
||||
if ok {
|
||||
|
||||
videoID := id[0]
|
||||
video := getVideo(videoID)
|
||||
|
||||
if video.Id == "" { //video not found, or empty ID
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte("{}"))
|
||||
return
|
||||
}
|
||||
|
||||
videoBytes, err := json.Marshal(video)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w.Write(videoBytes)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
videos := getVideos()
|
||||
videoBytes, err := json.Marshal(videos)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w.Write(videoBytes)
|
||||
}
|
||||
```
|
||||
|
||||
We can also update our `/update POST` endpoint to be able to update a single video
|
||||
|
||||
```
|
||||
func HandleUpdateVideos(w http.ResponseWriter, r *http.Request){
|
||||
|
||||
if r.Method == "POST" {
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, ok := r.URL.Query()["id"]
|
||||
if ok {
|
||||
|
||||
var video video
|
||||
err = json.Unmarshal(body, &video)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
fmt.Fprintf(w, "Bad request")
|
||||
}
|
||||
|
||||
saveVideo(video)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
var videos []video
|
||||
err = json.Unmarshal(body, &videos)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
fmt.Fprintf(w, "Bad request")
|
||||
}
|
||||
|
||||
saveVideos(videos)
|
||||
return
|
||||
|
||||
} else {
|
||||
w.WriteHeader(405)
|
||||
fmt.Fprintf(w, "Method not Supported!")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Build our Docker container
|
||||
|
||||
Full `dockerfile` :
|
||||
|
||||
```
|
||||
FROM golang:1.15-alpine as dev
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
FROM golang:1.15-alpine as build
|
||||
|
||||
WORKDIR /videos
|
||||
COPY ./videos/* /videos/
|
||||
RUN go build -o videos
|
||||
|
||||
FROM alpine as runtime
|
||||
COPY --from=build /videos/videos /
|
||||
CMD ./videos
|
||||
|
||||
```
|
||||
|
||||
Build :
|
||||
```
|
||||
cd golang\introduction\part-5.database.redis
|
||||
docker build . -t videos
|
||||
```
|
||||
|
||||
Run :
|
||||
```
|
||||
docker run -it -p 80:80 `
|
||||
--net redis `
|
||||
-e REDIS_SENTINELS="sentinel-0:5000,sentinel-1:5000,sentinel-2:5000" `
|
||||
-e REDIS_MASTER_NAME="mymaster" `
|
||||
-e REDIS_PASSWORD="a-very-complex-password-here" `
|
||||
videos
|
||||
```
|
5
golang/introduction/part-5.database.redis/videos/go.mod
Normal file
5
golang/introduction/part-5.database.redis/videos/go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module videos
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/go-redis/redis/v8 v8.11.4
|
89
golang/introduction/part-5.database.redis/videos/go.sum
Normal file
89
golang/introduction/part-5.database.redis/videos/go.sum
Normal file
@ -0,0 +1,89 @@
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
|
||||
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
|
||||
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
113
golang/introduction/part-5.database.redis/videos/main.go
Normal file
113
golang/introduction/part-5.database.redis/videos/main.go
Normal file
@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"fmt"
|
||||
"os"
|
||||
"context"
|
||||
"strings"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
var ctx = context.Background()
|
||||
var redisClient *redis.Client
|
||||
|
||||
func main() {
|
||||
|
||||
var redis_sentinels = os.Getenv("REDIS_SENTINELS")
|
||||
var redis_master = os.Getenv("REDIS_MASTER_NAME")
|
||||
var redis_password = os.Getenv("REDIS_PASSWORD")
|
||||
|
||||
sentinelAddrs := strings.Split(redis_sentinels, ",")
|
||||
|
||||
rdb := redis.NewFailoverClient(&redis.FailoverOptions{
|
||||
MasterName: redis_master,
|
||||
SentinelAddrs: sentinelAddrs,
|
||||
Password: redis_password,
|
||||
DB: 0,
|
||||
})
|
||||
|
||||
redisClient = rdb
|
||||
|
||||
rdb.Ping(ctx)
|
||||
|
||||
http.HandleFunc("/", HandleGetVideos)
|
||||
http.HandleFunc("/update", HandleUpdateVideos)
|
||||
|
||||
http.ListenAndServe(":80", nil)
|
||||
}
|
||||
|
||||
func HandleGetVideos(w http.ResponseWriter, r *http.Request){
|
||||
|
||||
id, ok := r.URL.Query()["id"]
|
||||
|
||||
if ok {
|
||||
videoID := id[0]
|
||||
video := getVideo(videoID)
|
||||
|
||||
if video.Id == "" { //video not found, or empty ID
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte("{}"))
|
||||
return
|
||||
}
|
||||
|
||||
videoBytes, err := json.Marshal(video)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.Write(videoBytes)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
videos := getVideos()
|
||||
videoBytes, err := json.Marshal(videos)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
w.Write(videoBytes)
|
||||
}
|
||||
|
||||
func HandleUpdateVideos(w http.ResponseWriter, r *http.Request){
|
||||
|
||||
if r.Method == "POST" {
|
||||
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, ok := r.URL.Query()["id"]
|
||||
|
||||
if ok {
|
||||
var video video
|
||||
err = json.Unmarshal(body, &video)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
fmt.Fprintf(w, "Bad request")
|
||||
}
|
||||
|
||||
saveVideo(video)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
var videos []video
|
||||
err = json.Unmarshal(body, &videos)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
fmt.Fprintf(w, "Bad request")
|
||||
}
|
||||
|
||||
saveVideos(videos)
|
||||
return
|
||||
|
||||
} else {
|
||||
w.WriteHeader(405)
|
||||
fmt.Fprintf(w, "Method not Supported!")
|
||||
}
|
||||
}
|
64
golang/introduction/part-5.database.redis/videos/videos.go
Normal file
64
golang/introduction/part-5.database.redis/videos/videos.go
Normal file
@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
type video struct {
|
||||
Id string
|
||||
Title string
|
||||
Description string
|
||||
Imageurl string
|
||||
Url string
|
||||
}
|
||||
|
||||
func getVideos()(videos []video){
|
||||
|
||||
keys, err := redisClient.Keys(ctx,"*").Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
video := getVideo(key)
|
||||
videos = append(videos, video)
|
||||
}
|
||||
return videos
|
||||
}
|
||||
|
||||
func getVideo(id string)(video video) {
|
||||
|
||||
value, err := redisClient.Get(ctx, id).Result()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err != redis.Nil {
|
||||
err = json.Unmarshal([]byte(value), &video)
|
||||
}
|
||||
|
||||
return video
|
||||
}
|
||||
|
||||
func saveVideo(video video)(){
|
||||
|
||||
videoBytes, err := json.Marshal(video)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = redisClient.Set(ctx, video.Id, videoBytes, 0).Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func saveVideos(videos []video)(){
|
||||
for _, video := range videos {
|
||||
saveVideo(video)
|
||||
}
|
||||
}
|
431
golang/introduction/readme.md
Normal file
431
golang/introduction/readme.md
Normal file
@ -0,0 +1,431 @@
|
||||
# Introduction to Learning Go
|
||||
|
||||
<a href="https://youtu.be/jpKysZwllVw" title="golang-part-1"><img src="https://i.ytimg.com/vi/jpKysZwllVw/hqdefault.jpg" width="20%" alt="introduction to Go part 1" /></a>
|
||||
|
||||
Go can be downloaded from [golang.org](https://golang.org/doc/install) <br/>
|
||||
|
||||
Test your `go` installation:
|
||||
|
||||
```
|
||||
go version
|
||||
```
|
||||
|
||||
# Run Go in Docker
|
||||
|
||||
We can also run go in a small docker container: <br/>
|
||||
|
||||
```
|
||||
cd golang\introduction
|
||||
|
||||
docker build --target dev . -t go
|
||||
docker run -it -v ${PWD}:/work go sh
|
||||
go version
|
||||
|
||||
```
|
||||
|
||||
# Code Structure
|
||||
|
||||
https://golang.org/doc/code.html
|
||||
|
||||
* Package:
|
||||
- Source files in same directory that are compiled together
|
||||
- Have visibility on all source files in the same package
|
||||
|
||||
* Modules:
|
||||
- Collection of packages that are released together
|
||||
|
||||
Our repository can contain one or more go modules, but usually 1.
|
||||
- At the root of the repo
|
||||
|
||||
`go.mod` Declares module path + import path for packages. (Where to download them)
|
||||
- When we write our own program, we can define a module path
|
||||
- This allows us to publish our code (if we want), so others can download it
|
||||
- The module path could be something like `github.com/google/go-cmp`
|
||||
- Makes it easy for other programs to consume our module
|
||||
|
||||
# Our first Program
|
||||
|
||||
* Create a folder containing our application
|
||||
|
||||
```
|
||||
mkdir app
|
||||
|
||||
```
|
||||
|
||||
* Define a module path (github.com/docker-development-youtube-series/golang/introdouction/app)
|
||||
|
||||
```
|
||||
# change directory to your application source code
|
||||
|
||||
cd app
|
||||
|
||||
# create a go module file
|
||||
|
||||
go mod init github.com/docker-development-youtube-series/golang/introdouction/app
|
||||
|
||||
```
|
||||
|
||||
* Create basic source code
|
||||
|
||||
In the `app` folder, create a program called `app.go`
|
||||
Paste the following content into it.
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello, world.")
|
||||
}
|
||||
```
|
||||
|
||||
* Run your application code
|
||||
|
||||
You can run your application
|
||||
|
||||
```
|
||||
go run app.go
|
||||
```
|
||||
|
||||
# Building our Program
|
||||
|
||||
Build your application into a static binary: <br/>
|
||||
|
||||
```
|
||||
go build
|
||||
```
|
||||
|
||||
This will produce a compiled program called `app`
|
||||
You can run this program easily:
|
||||
|
||||
```
|
||||
./app
|
||||
```
|
||||
|
||||
# Install your application (optional)
|
||||
|
||||
"This command builds the app command, producing an executable binary. <br/>
|
||||
It then installs that binary as $HOME/go/bin/app (or, under Windows, %USERPROFILE%\go\bin\app.exe)"
|
||||
|
||||
```
|
||||
go install github.com/docker-development-youtube-series/golang/introdouction/app
|
||||
```
|
||||
|
||||
# The Code
|
||||
|
||||
## Functions
|
||||
|
||||
In the video we cover writing functions. </br>
|
||||
It allows us to execute a block of code <br/>
|
||||
You want to give your function a single purpose <br/>
|
||||
Functions can have an input and return an output <br/>
|
||||
Well thought out functions makes it easier to write tests <br/>
|
||||
|
||||
Instead of doing a boring `x + y` function that adds two numbers, let's do something a little
|
||||
more realistic but still basic:
|
||||
|
||||
```
|
||||
// This function returns some data
|
||||
// The data could be coming from a database, or a file.
|
||||
// The person calling the function should not care
|
||||
// Since the function does not leak its Data provider
|
||||
|
||||
func getData(inputs)(outputs){
|
||||
}
|
||||
|
||||
// functions can take multiple inputs, and return multiple outputs
|
||||
|
||||
// lets say we have 1) customers and 2) the cities they are from
|
||||
// we may want to 1) get a list of customers and 2) get a list of cities
|
||||
// therefore we have 2 types of data, 1) customers 2) cities
|
||||
// let's improve our function so its gets data based on the type
|
||||
|
||||
func getData(customerId int) (customer string) {
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
To hold data in programming languages, we use variables. <br/>
|
||||
Variables take up space in memory, so we want to keep it minimal. <br/>
|
||||
Let's declare variables in our function
|
||||
|
||||
```
|
||||
func getData(customerId int) (customer string) {
|
||||
var firstName = "Marcel"
|
||||
lastName := "Dempers"
|
||||
|
||||
fullName := firstName + " " + lastName
|
||||
return fullName
|
||||
|
||||
//or we can return the computation instead of adding another variable!
|
||||
return firstName + " " + lastName
|
||||
|
||||
//or we dont even need to declare variables :)
|
||||
return "Marcel Dempers"
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Control Flows (if\else)
|
||||
|
||||
You can see we're not using the `customerId` input in our function. <br/>
|
||||
Let's use it! <br/>
|
||||
|
||||
Control flows allow us to add "rules" to our code. </br>
|
||||
"If this is the case, then do that, else do something else".
|
||||
|
||||
So let's say we have a customer ID 1 coming in, we may only want to
|
||||
return our customer if it matches the `customerId`
|
||||
|
||||
```
|
||||
|
||||
func getData(customerId int) (customer string) {
|
||||
|
||||
if customerId == 1 {
|
||||
return "Marcel Dempers"
|
||||
} else if customerId == 2 {
|
||||
return "Bob Smith"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
Let's invoke our function :
|
||||
|
||||
```
|
||||
//in the main() function
|
||||
|
||||
//get our customer
|
||||
customer := getData(1)
|
||||
fmt.Println(customer)
|
||||
|
||||
//get the wrong customer
|
||||
customer := getData(3)
|
||||
fmt.Println(customer)
|
||||
|
||||
|
||||
```
|
||||
## Arrays
|
||||
|
||||
At the moment, we can only return 1 customer at a time on our function. <br/>
|
||||
Realistically we need the ability to return more data, not just a single customer. <br/>
|
||||
|
||||
Arrays allow us to make a collection of variables of the same type. <br/>
|
||||
We can now return a list of customers! <br/>
|
||||
|
||||
Let's change our function to get an array of customers!
|
||||
|
||||
```
|
||||
func getData() (customers [2]string) {
|
||||
//create 1 record
|
||||
customer := "Marcel Dempers"
|
||||
|
||||
//assign our customer to the array
|
||||
customers[0] = customer
|
||||
|
||||
//OR we can assign it like this
|
||||
customers[1] = "Bob Smith"
|
||||
|
||||
//send it back to the caller
|
||||
return customers
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Now we also have to change our calling function to expect an array:
|
||||
|
||||
```
|
||||
customers := getData()
|
||||
|
||||
fmt.Println(customers)
|
||||
|
||||
```
|
||||
|
||||
## Slices
|
||||
|
||||
Since arrays are fixed size, Slices are a dynamically-sized view into arrays.
|
||||
Let's create a slice instead of array so we can add customers dynamically!
|
||||
|
||||
```
|
||||
func getData() (customers []string) {
|
||||
|
||||
//initialise our slice of type string
|
||||
customers = []string{ "Marcel Dempers", "Bob Smith", "John Smith"}
|
||||
|
||||
//add more legendary customers dynamically
|
||||
customers = append(customers, "Ben Spain")
|
||||
customers = append(customers, "Aleem Janmohamed")
|
||||
customers = append(customers, "Jamie le Notre")
|
||||
customers = append(customers, "Victor Savkov")
|
||||
customers = append(customers, "P The Admin")
|
||||
customers = append(customers, "Adrian Oprea")
|
||||
customers = append(customers, "Jonathan D")
|
||||
|
||||
//send it back to the caller
|
||||
return customers
|
||||
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Loops
|
||||
|
||||
Loops are used to iterate over collections, lists, arrays etc. <br/>
|
||||
Let's say we need to loop through our customers
|
||||
|
||||
In the `main()` function, we can grab the list of customers and loop them.
|
||||
In this demo, we'll cover a basic for loop, but there are several approaches to writing loops.
|
||||
|
||||
```
|
||||
//loop forever
|
||||
for {
|
||||
//any code in here will run forever!
|
||||
|
||||
fmt.Println("Infinite Loop 1")
|
||||
time.Sleep(time.Second)
|
||||
|
||||
//unless we break out the loop like this
|
||||
break
|
||||
}
|
||||
|
||||
//loop for x number of loops
|
||||
for x := 0; x < 10; x++ {
|
||||
|
||||
//any code in here will run 10 times! (unless we break!)
|
||||
fmt.Println(customers[x])
|
||||
|
||||
}
|
||||
|
||||
//loop for ALL our customer
|
||||
|
||||
for x, customer := range customers {
|
||||
|
||||
//we can access the "customer" variable in this approach
|
||||
customer = customers[x]
|
||||
fmt.Println(customer)
|
||||
|
||||
//OR
|
||||
//we can use the supplied customer from the loop
|
||||
// and silence the x variable, replace it with a _ character
|
||||
fmt.Println(customer)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Structs
|
||||
|
||||
So far so good, however, customer data is not useful as strings. <br/>
|
||||
Customers can have a firstname, lastname, and more properties. <br/>
|
||||
|
||||
For this purpose we'd like to group some variables into a single variable. <br/>
|
||||
This is what `struct` allows us to do. <br/>
|
||||
Let's create a `struct` for our customer
|
||||
|
||||
Let's create a new `go` file called `customers.go`
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
Customer struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
FullName string
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Let's put it all together:
|
||||
|
||||
In `customers.go`, let's create a function to get customers
|
||||
|
||||
```
|
||||
func GetCustomers()(customers []Customer) {
|
||||
|
||||
//we can declare customers like this:
|
||||
marcel := Customer{ FirstName: "Marcel", LastName: "Dempers" }
|
||||
|
||||
customers = append(customers,
|
||||
Customer{ FirstName: "Marcel", LastName: "Dempers" },
|
||||
Customer{ FirstName: "Ben", LastName: "Spain" },
|
||||
Customer{ FirstName: "Aleem", LastName: "Janmohamed" },
|
||||
Customer{ FirstName: "Jamie", LastName: "le Notre" },
|
||||
Customer{ FirstName: "Victor", LastName: "Savkov" },
|
||||
Customer{ FirstName: "P", LastName: "The Admin" },
|
||||
Customer{ FirstName: "Adrian", LastName: "Oprea" },
|
||||
Customer{ FirstName: "Jonathan", LastName: "D" },
|
||||
)
|
||||
|
||||
return customers
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
In `main()` we can now call our shiny new function
|
||||
|
||||
|
||||
```
|
||||
customers := GetCustomers()
|
||||
|
||||
for _, customer := range customers {
|
||||
//we can access the "customer" variable in this approach
|
||||
fmt.Println(customer)
|
||||
}
|
||||
```
|
||||
|
||||
# Docker
|
||||
|
||||
For our dev environment, we have a simple image using `go` <br/>
|
||||
We also set a work directory and alias the target as `dev`
|
||||
|
||||
This means we can use this container layer as a development environment. <br/>
|
||||
Later down the track we can add debuggers in here for example. <br/>
|
||||
Checkout my debugging video for go: https://youtu.be/kToyI16IFxs <br/>
|
||||
|
||||
|
||||
## Development environment
|
||||
|
||||
```
|
||||
FROM golang:1.15 as dev
|
||||
|
||||
WORKDIR /work
|
||||
```
|
||||
|
||||
## Building our code
|
||||
|
||||
```
|
||||
FROM golang:1.15 as build
|
||||
|
||||
WORKDIR /app
|
||||
COPY ./app/* /app/
|
||||
RUN go build -o app
|
||||
```
|
||||
|
||||
## The Runtime
|
||||
|
||||
```
|
||||
FROM alpine as runtime
|
||||
COPY --from=build /app/app /
|
||||
CMD ./app
|
||||
```
|
||||
|
||||
## Building the Container
|
||||
|
||||
```
|
||||
docker build . -t customer-app
|
||||
|
||||
```
|
||||
|
||||
## Running the Container
|
||||
|
||||
```
|
||||
docker run customer-app
|
||||
```
|
3
golang/secrets/secret.json
Normal file
3
golang/secrets/secret.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"api_key" : "somesecretgoeshere"
|
||||
}
|
58
golang/src/main.go
Normal file
58
golang/src/main.go
Normal file
@ -0,0 +1,58 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt" //to print messages to stdout
|
||||
"log" //logging :)
|
||||
//our web server that will host the mock
|
||||
"github.com/buaazp/fasthttprouter"
|
||||
"github.com/valyala/fasthttp"
|
||||
"os"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
var configuration []byte
|
||||
var secret []byte
|
||||
|
||||
func Response(ctx *fasthttp.RequestCtx) {
|
||||
fmt.Fprintf(ctx, "Hello")
|
||||
}
|
||||
|
||||
func Status(ctx *fasthttp.RequestCtx) {
|
||||
fmt.Fprintf(ctx, "ok")
|
||||
}
|
||||
|
||||
func ReadConfig(){
|
||||
fmt.Println("reading config...")
|
||||
config, e := ioutil.ReadFile("/configs/config.json")
|
||||
if e != nil {
|
||||
fmt.Printf("Error reading config file: %v\n", e)
|
||||
os.Exit(1)
|
||||
}
|
||||
configuration = config
|
||||
fmt.Println("config loaded!")
|
||||
|
||||
}
|
||||
|
||||
func ReadSecret(){
|
||||
fmt.Println("reading secret...")
|
||||
s, e := ioutil.ReadFile("/secrets/secret.json")
|
||||
if e != nil {
|
||||
fmt.Printf("Error reading secret file: %v\n", e)
|
||||
os.Exit(1)
|
||||
}
|
||||
secret = s
|
||||
fmt.Println("secret loaded!")
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
fmt.Println("starting...")
|
||||
ReadConfig()
|
||||
ReadSecret()
|
||||
router := fasthttprouter.New()
|
||||
router.GET("/", Response)
|
||||
router.GET("/status", Status)
|
||||
|
||||
log.Fatal(fasthttp.ListenAndServe(":5000", router.Handler))
|
||||
}
|
11
hashicorp/vault-2022/consul-values.yaml
Normal file
11
hashicorp/vault-2022/consul-values.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
global:
|
||||
datacenter: vault-kubernetes-guide
|
||||
|
||||
client:
|
||||
enabled: true
|
||||
|
||||
server:
|
||||
replicas: 1
|
||||
bootstrapExpect: 1
|
||||
disruptionBudget:
|
||||
maxUnavailable: 0
|
@ -0,0 +1,39 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: basic-secret
|
||||
labels:
|
||||
app: basic-secret
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: basic-secret
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
vault.hashicorp.com/tls-skip-verify: "true"
|
||||
vault.hashicorp.com/agent-inject-secret-helloworld: "secret/basic-secret/helloworld"
|
||||
vault.hashicorp.com/agent-inject-template-helloworld: |
|
||||
{{- with secret "secret/basic-secret/helloworld" -}}
|
||||
{
|
||||
"username" : "{{ .Data.username }}",
|
||||
"password" : "{{ .Data.password }}"
|
||||
}
|
||||
{{- end }}
|
||||
vault.hashicorp.com/role: "basic-secret-role"
|
||||
labels:
|
||||
app: basic-secret
|
||||
spec:
|
||||
serviceAccountName: basic-secret
|
||||
containers:
|
||||
- name: app
|
||||
image: jweissig/app:0.0.1
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: basic-secret
|
||||
labels:
|
||||
app: basic-secret
|
52
hashicorp/vault-2022/example-apps/basic-secret/readme.md
Normal file
52
hashicorp/vault-2022/example-apps/basic-secret/readme.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Basic Secret Injection
|
||||
|
||||
In order for us to start using secrets in vault, we need to setup a policy.
|
||||
|
||||
```
|
||||
#Create a role for our app
|
||||
|
||||
kubectl -n vault exec -it vault-0 -- sh
|
||||
|
||||
vault write auth/kubernetes/role/basic-secret-role \
|
||||
bound_service_account_names=basic-secret \
|
||||
bound_service_account_namespaces=example-app \
|
||||
policies=basic-secret-policy \
|
||||
ttl=1h
|
||||
```
|
||||
|
||||
The above maps our Kubernetes service account, used by our pod, to a policy.
|
||||
Now lets create the policy to map our service account to a bunch of secrets
|
||||
|
||||
```
|
||||
kubectl -n vault exec -it vault-0 -- sh
|
||||
|
||||
cat <<EOF > /home/vault/app-policy.hcl
|
||||
path "secret/basic-secret/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
EOF
|
||||
vault policy write basic-secret-policy /home/vault/app-policy.hcl
|
||||
```
|
||||
|
||||
Now our service account for our pod can access all secrets under `secret/basic-secret/*`
|
||||
Lets create some secrets.
|
||||
|
||||
```
|
||||
kubectl -n vault exec -it vault-0 -- sh
|
||||
vault secrets enable -path=secret/ kv
|
||||
vault kv put secret/basic-secret/helloworld username=dbuser password=sUp3rS3cUr3P@ssw0rd
|
||||
```
|
||||
|
||||
Lets deploy our app and see if it works:
|
||||
|
||||
```
|
||||
kubectl create ns example-app
|
||||
kubectl -n example-app apply -f ./example-apps/basic-secret/deployment.yaml
|
||||
kubectl -n example-app get pods
|
||||
```
|
||||
|
||||
Once the pod is ready, the secret is injected into the pod at the following location:
|
||||
|
||||
```
|
||||
kubectl -n example-app exec <pod-name> -- sh -c "cat /vault/secrets/helloworld"
|
||||
```
|
7
hashicorp/vault-2022/kind.yaml
Normal file
7
hashicorp/vault-2022/kind.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
kind: Cluster
|
||||
apiVersion: kind.x-k8s.io/v1alpha4
|
||||
nodes:
|
||||
- role: control-plane
|
||||
- role: worker
|
||||
- role: worker
|
||||
- role: worker
|
626
hashicorp/vault-2022/manifests/consul.yaml
Normal file
626
hashicorp/vault-2022/manifests/consul.yaml
Normal file
@ -0,0 +1,626 @@
|
||||
---
|
||||
# Source: consul/templates/server-disruptionbudget.yaml
|
||||
# PodDisruptionBudget to prevent degrading the server cluster through
|
||||
# voluntary cluster changes.
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: consul-consul-server
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: server
|
||||
spec:
|
||||
maxUnavailable: 0
|
||||
selector:
|
||||
matchLabels:
|
||||
app: consul
|
||||
release: "consul"
|
||||
component: server
|
||||
---
|
||||
# Source: consul/templates/client-serviceaccount.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: consul-consul-client
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: client
|
||||
---
|
||||
# Source: consul/templates/server-serviceaccount.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: consul-consul-server
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: server
|
||||
---
|
||||
# Source: consul/templates/client-config-configmap.yaml
|
||||
# ConfigMap with extra configuration specified directly to the chart
|
||||
# for client agents only.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: consul-consul-client-config
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: client
|
||||
data:
|
||||
extra-from-values.json: |-
|
||||
{}
|
||||
|
||||
central-config.json: |-
|
||||
{
|
||||
"enable_central_service_config": true
|
||||
}
|
||||
---
|
||||
# Source: consul/templates/server-config-configmap.yaml
|
||||
# StatefulSet to run the actual Consul server cluster.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: consul-consul-server-config
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: server
|
||||
data:
|
||||
extra-from-values.json: |-
|
||||
{}
|
||||
|
||||
central-config.json: |-
|
||||
{
|
||||
"enable_central_service_config": true
|
||||
}
|
||||
---
|
||||
# Source: consul/templates/client-role.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: consul-consul-client
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: client
|
||||
rules: []
|
||||
---
|
||||
# Source: consul/templates/server-role.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: consul-consul-server
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: server
|
||||
rules: []
|
||||
---
|
||||
# Source: consul/templates/client-rolebinding.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: consul-consul-client
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: client
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: consul-consul-client
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: consul-consul-client
|
||||
---
|
||||
# Source: consul/templates/server-rolebinding.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: consul-consul-server
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: server
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: consul-consul-server
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: consul-consul-server
|
||||
---
|
||||
# Source: consul/templates/dns-service.yaml
|
||||
# Service for Consul DNS.
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: consul-consul-dns
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: dns
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- name: dns-tcp
|
||||
port: 53
|
||||
protocol: "TCP"
|
||||
targetPort: dns-tcp
|
||||
- name: dns-udp
|
||||
port: 53
|
||||
protocol: "UDP"
|
||||
targetPort: dns-udp
|
||||
selector:
|
||||
app: consul
|
||||
release: "consul"
|
||||
hasDNS: "true"
|
||||
---
|
||||
# Source: consul/templates/server-service.yaml
|
||||
# Headless service for Consul server DNS entries. This service should only
|
||||
# point to Consul servers. For access to an agent, one should assume that
|
||||
# the agent is installed locally on the node and the NODE_IP should be used.
|
||||
# If the node can't run a Consul agent, then this service can be used to
|
||||
# communicate directly to a server agent.
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: consul-consul-server
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: server
|
||||
annotations:
|
||||
# This must be set in addition to publishNotReadyAddresses due
|
||||
# to an open issue where it may not work:
|
||||
# https://github.com/kubernetes/kubernetes/issues/58662
|
||||
service.alpha.kubernetes.io/tolerate-unready-endpoints: "true"
|
||||
spec:
|
||||
clusterIP: None
|
||||
# We want the servers to become available even if they're not ready
|
||||
# since this DNS is also used for join operations.
|
||||
publishNotReadyAddresses: true
|
||||
ports:
|
||||
- name: http
|
||||
port: 8500
|
||||
targetPort: 8500
|
||||
- name: serflan-tcp
|
||||
protocol: "TCP"
|
||||
port: 8301
|
||||
targetPort: 8301
|
||||
- name: serflan-udp
|
||||
protocol: "UDP"
|
||||
port: 8301
|
||||
targetPort: 8301
|
||||
- name: serfwan-tcp
|
||||
protocol: "TCP"
|
||||
port: 8302
|
||||
targetPort: 8302
|
||||
- name: serfwan-udp
|
||||
protocol: "UDP"
|
||||
port: 8302
|
||||
targetPort: 8302
|
||||
- name: server
|
||||
port: 8300
|
||||
targetPort: 8300
|
||||
- name: dns-tcp
|
||||
protocol: "TCP"
|
||||
port: 8600
|
||||
targetPort: dns-tcp
|
||||
- name: dns-udp
|
||||
protocol: "UDP"
|
||||
port: 8600
|
||||
targetPort: dns-udp
|
||||
selector:
|
||||
app: consul
|
||||
release: "consul"
|
||||
component: server
|
||||
---
|
||||
# Source: consul/templates/ui-service.yaml
|
||||
# UI Service for Consul Server
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: consul-consul-ui
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: ui
|
||||
spec:
|
||||
selector:
|
||||
app: consul
|
||||
release: "consul"
|
||||
component: server
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 8500
|
||||
---
|
||||
# Source: consul/templates/client-daemonset.yaml
|
||||
# DaemonSet to run the Consul clients on every node.
|
||||
apiVersion: apps/v1
|
||||
kind: DaemonSet
|
||||
metadata:
|
||||
name: consul-consul
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: client
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
release: consul
|
||||
component: client
|
||||
hasDNS: "true"
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
release: consul
|
||||
component: client
|
||||
hasDNS: "true"
|
||||
annotations:
|
||||
"consul.hashicorp.com/connect-inject": "false"
|
||||
"consul.hashicorp.com/config-checksum": 797b3593a73b78fc74f3b1e3b978107b3022d4649802185631f959f000234331
|
||||
spec:
|
||||
terminationGracePeriodSeconds: 10
|
||||
serviceAccountName: consul-consul-client
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
runAsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
runAsUser: 100
|
||||
|
||||
volumes:
|
||||
- name: data
|
||||
emptyDir: {}
|
||||
- name: config
|
||||
configMap:
|
||||
name: consul-consul-client-config
|
||||
containers:
|
||||
- name: consul
|
||||
image: "hashicorp/consul:1.11.1"
|
||||
env:
|
||||
- name: ADVERTISE_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: NODE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: spec.nodeName
|
||||
- name: HOST_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.hostIP
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: CONSUL_DISABLE_PERM_MGMT
|
||||
value: "true"
|
||||
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-ec"
|
||||
- |
|
||||
CONSUL_FULLNAME="consul-consul"
|
||||
|
||||
mkdir -p /consul/extra-config
|
||||
cp /consul/config/extra-from-values.json /consul/extra-config/extra-from-values.json
|
||||
[ -n "${HOST_IP}" ] && sed -Ei "s|HOST_IP|${HOST_IP?}|g" /consul/extra-config/extra-from-values.json
|
||||
[ -n "${POD_IP}" ] && sed -Ei "s|POD_IP|${POD_IP?}|g" /consul/extra-config/extra-from-values.json
|
||||
[ -n "${HOSTNAME}" ] && sed -Ei "s|HOSTNAME|${HOSTNAME?}|g" /consul/extra-config/extra-from-values.json
|
||||
|
||||
exec /usr/local/bin/docker-entrypoint.sh consul agent \
|
||||
-node="${NODE}" \
|
||||
-advertise="${ADVERTISE_IP}" \
|
||||
-bind=0.0.0.0 \
|
||||
-client=0.0.0.0 \
|
||||
-node-meta=host-ip:${HOST_IP} \
|
||||
-node-meta=pod-name:${HOSTNAME} \
|
||||
-hcl='leave_on_terminate = true' \
|
||||
-hcl='ports { grpc = 8502 }' \
|
||||
-config-dir=/consul/config \
|
||||
-datacenter=vault-kubernetes-guide \
|
||||
-data-dir=/consul/data \
|
||||
-retry-join="${CONSUL_FULLNAME}-server-0.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8301" \
|
||||
-config-file=/consul/extra-config/extra-from-values.json \
|
||||
-domain=consul
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /consul/data
|
||||
- name: config
|
||||
mountPath: /consul/config
|
||||
ports:
|
||||
- containerPort: 8500
|
||||
hostPort: 8500
|
||||
name: http
|
||||
- containerPort: 8502
|
||||
hostPort: 8502
|
||||
name: grpc
|
||||
- containerPort: 8301
|
||||
protocol: "TCP"
|
||||
name: serflan-tcp
|
||||
- containerPort: 8301
|
||||
protocol: "UDP"
|
||||
name: serflan-udp
|
||||
- containerPort: 8600
|
||||
name: dns-tcp
|
||||
protocol: "TCP"
|
||||
- containerPort: 8600
|
||||
name: dns-udp
|
||||
protocol: "UDP"
|
||||
readinessProbe:
|
||||
# NOTE(mitchellh): when our HTTP status endpoints support the
|
||||
# proper status codes, we should switch to that. This is temporary.
|
||||
exec:
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-ec"
|
||||
- |
|
||||
curl http://127.0.0.1:8500/v1/status/leader \
|
||||
2>/dev/null | grep -E '".+"'
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
securityContext:
|
||||
null
|
||||
---
|
||||
# Source: consul/templates/server-statefulset.yaml
|
||||
# StatefulSet to run the actual Consul server cluster.
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: consul-consul-server
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
component: server
|
||||
spec:
|
||||
serviceName: consul-consul-server
|
||||
podManagementPolicy: Parallel
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
release: consul
|
||||
component: server
|
||||
hasDNS: "true"
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
release: consul
|
||||
component: server
|
||||
hasDNS: "true"
|
||||
annotations:
|
||||
"consul.hashicorp.com/connect-inject": "false"
|
||||
"consul.hashicorp.com/config-checksum": c9b100f895d5bda6a5c8bbebac73e1ab5bdc4cad06b04e72eb1b620677bfe41d
|
||||
spec:
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchLabels:
|
||||
app: consul
|
||||
release: "consul"
|
||||
component: server
|
||||
topologyKey: kubernetes.io/hostname
|
||||
terminationGracePeriodSeconds: 30
|
||||
serviceAccountName: consul-consul-server
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
runAsGroup: 1000
|
||||
runAsNonRoot: true
|
||||
runAsUser: 100
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: consul-consul-server-config
|
||||
containers:
|
||||
- name: consul
|
||||
image: "hashicorp/consul:1.11.1"
|
||||
env:
|
||||
- name: ADVERTISE_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: HOST_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.hostIP
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: CONSUL_DISABLE_PERM_MGMT
|
||||
value: "true"
|
||||
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-ec"
|
||||
- |
|
||||
CONSUL_FULLNAME="consul-consul"
|
||||
|
||||
mkdir -p /consul/extra-config
|
||||
cp /consul/config/extra-from-values.json /consul/extra-config/extra-from-values.json
|
||||
[ -n "${HOST_IP}" ] && sed -Ei "s|HOST_IP|${HOST_IP?}|g" /consul/extra-config/extra-from-values.json
|
||||
[ -n "${POD_IP}" ] && sed -Ei "s|POD_IP|${POD_IP?}|g" /consul/extra-config/extra-from-values.json
|
||||
[ -n "${HOSTNAME}" ] && sed -Ei "s|HOSTNAME|${HOSTNAME?}|g" /consul/extra-config/extra-from-values.json
|
||||
|
||||
exec /usr/local/bin/docker-entrypoint.sh consul agent \
|
||||
-advertise="${ADVERTISE_IP}" \
|
||||
-bind=0.0.0.0 \
|
||||
-bootstrap-expect=1 \
|
||||
-client=0.0.0.0 \
|
||||
-config-dir=/consul/config \
|
||||
-datacenter=vault-kubernetes-guide \
|
||||
-data-dir=/consul/data \
|
||||
-domain=consul \
|
||||
-hcl="connect { enabled = true }" \
|
||||
-ui \
|
||||
-retry-join="${CONSUL_FULLNAME}-server-0.${CONSUL_FULLNAME}-server.${NAMESPACE}.svc:8301" \
|
||||
-serf-lan-port=8301 \
|
||||
-config-file=/consul/extra-config/extra-from-values.json \
|
||||
-server
|
||||
volumeMounts:
|
||||
- name: data-vault
|
||||
mountPath: /consul/data
|
||||
- name: config
|
||||
mountPath: /consul/config
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8500
|
||||
- name: serflan-tcp
|
||||
containerPort: 8301
|
||||
protocol: "TCP"
|
||||
- name: serflan-udp
|
||||
containerPort: 8301
|
||||
protocol: "UDP"
|
||||
- name: serfwan-tcp
|
||||
containerPort: 8302
|
||||
protocol: "TCP"
|
||||
- name: serfwan-udp
|
||||
containerPort: 8302
|
||||
protocol: "UDP"
|
||||
- name: server
|
||||
containerPort: 8300
|
||||
- name: dns-tcp
|
||||
containerPort: 8600
|
||||
protocol: "TCP"
|
||||
- name: dns-udp
|
||||
containerPort: 8600
|
||||
protocol: "UDP"
|
||||
readinessProbe:
|
||||
# NOTE(mitchellh): when our HTTP status endpoints support the
|
||||
# proper status codes, we should switch to that. This is temporary.
|
||||
exec:
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-ec"
|
||||
- |
|
||||
curl http://127.0.0.1:8500/v1/status/leader \
|
||||
2>/dev/null | grep -E '".+"'
|
||||
failureThreshold: 2
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 3
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 5
|
||||
resources:
|
||||
limits:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 100Mi
|
||||
securityContext:
|
||||
null
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data-vault
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
---
|
||||
# Source: consul/templates/tests/test-runner.yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "consul-consul-test"
|
||||
namespace: vault
|
||||
labels:
|
||||
app: consul
|
||||
chart: consul-helm
|
||||
heritage: Helm
|
||||
release: consul
|
||||
annotations:
|
||||
"helm.sh/hook": test-success
|
||||
spec:
|
||||
containers:
|
||||
- name: consul-test
|
||||
image: "hashicorp/consul:1.11.1"
|
||||
env:
|
||||
- name: HOST_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.hostIP
|
||||
- name: CONSUL_HTTP_ADDR
|
||||
value: http://$(HOST_IP):8500
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-ec"
|
||||
- |
|
||||
consul members | tee members.txt
|
||||
if [ $(grep -c consul-server members.txt) != $(grep consul-server members.txt | grep -c alive) ]
|
||||
then
|
||||
echo "Failed because not all consul servers are available"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
restartPolicy: Never
|
710
hashicorp/vault-2022/manifests/vault.yaml
Normal file
710
hashicorp/vault-2022/manifests/vault.yaml
Normal file
@ -0,0 +1,710 @@
|
||||
---
|
||||
# Source: vault/templates/server-disruptionbudget.yaml
|
||||
# PodDisruptionBudget to prevent degrading the server cluster through
|
||||
# voluntary cluster changes.
|
||||
apiVersion: policy/v1beta1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: vault
|
||||
namespace: vault
|
||||
labels:
|
||||
helm.sh/chart: vault-0.19.0
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
maxUnavailable: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
component: server
|
||||
---
|
||||
# Source: vault/templates/injector-serviceaccount.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: vault-agent-injector
|
||||
namespace: vault
|
||||
labels:
|
||||
app.kubernetes.io/name: vault-agent-injector
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
---
|
||||
# Source: vault/templates/server-serviceaccount.yaml
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: vault
|
||||
namespace: vault
|
||||
labels:
|
||||
helm.sh/chart: vault-0.19.0
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
---
|
||||
# Source: vault/templates/server-config-configmap.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: vault-config
|
||||
namespace: vault
|
||||
labels:
|
||||
helm.sh/chart: vault-0.19.0
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
data:
|
||||
extraconfig-from-values.hcl: |-
|
||||
disable_mlock = true
|
||||
ui = true
|
||||
|
||||
listener "tcp" {
|
||||
tls_disable = 0
|
||||
address = "0.0.0.0:8200"
|
||||
tls_cert_file = "/vault/userconfig/tls-server/tls.crt"
|
||||
tls_key_file = "/vault/userconfig/tls-server/tls.key"
|
||||
tls_min_version = "tls12"
|
||||
}
|
||||
|
||||
storage "consul" {
|
||||
path = "vault"
|
||||
address = "consul-consul-server:8500"
|
||||
}
|
||||
---
|
||||
# Source: vault/templates/injector-clusterrole.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: vault-agent-injector-clusterrole
|
||||
labels:
|
||||
app.kubernetes.io/name: vault-agent-injector
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
rules:
|
||||
- apiGroups: ["admissionregistration.k8s.io"]
|
||||
resources: ["mutatingwebhookconfigurations"]
|
||||
verbs:
|
||||
- "get"
|
||||
- "list"
|
||||
- "watch"
|
||||
- "patch"
|
||||
---
|
||||
# Source: vault/templates/injector-clusterrolebinding.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: vault-agent-injector-binding
|
||||
labels:
|
||||
app.kubernetes.io/name: vault-agent-injector
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: vault-agent-injector-clusterrole
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: vault-agent-injector
|
||||
namespace: vault
|
||||
---
|
||||
# Source: vault/templates/server-clusterrolebinding.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: vault-server-binding
|
||||
labels:
|
||||
helm.sh/chart: vault-0.19.0
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: system:auth-delegator
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: vault
|
||||
namespace: vault
|
||||
---
|
||||
# Source: vault/templates/server-discovery-role.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
namespace: vault
|
||||
name: vault-discovery-role
|
||||
labels:
|
||||
helm.sh/chart: vault-0.19.0
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "watch", "list", "update", "patch"]
|
||||
---
|
||||
# Source: vault/templates/server-discovery-rolebinding.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: vault-discovery-rolebinding
|
||||
namespace: vault
|
||||
labels:
|
||||
helm.sh/chart: vault-0.19.0
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: vault-discovery-role
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: vault
|
||||
namespace: vault
|
||||
---
|
||||
# Source: vault/templates/injector-service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: vault-agent-injector-svc
|
||||
namespace: vault
|
||||
labels:
|
||||
app.kubernetes.io/name: vault-agent-injector
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: https
|
||||
port: 443
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app.kubernetes.io/name: vault-agent-injector
|
||||
app.kubernetes.io/instance: vault
|
||||
component: webhook
|
||||
---
|
||||
# Source: vault/templates/server-ha-active-service.yaml
|
||||
# Service for active Vault pod
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: vault-active
|
||||
namespace: vault
|
||||
labels:
|
||||
helm.sh/chart: vault-0.19.0
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
annotations:
|
||||
|
||||
spec:
|
||||
publishNotReadyAddresses: true
|
||||
ports:
|
||||
- name: https
|
||||
port: 8200
|
||||
targetPort: 8200
|
||||
- name: https-internal
|
||||
port: 8201
|
||||
targetPort: 8201
|
||||
selector:
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
component: server
|
||||
vault-active: "true"
|
||||
---
|
||||
# Source: vault/templates/server-ha-standby-service.yaml
|
||||
# Service for standby Vault pod
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: vault-standby
|
||||
namespace: vault
|
||||
labels:
|
||||
helm.sh/chart: vault-0.19.0
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
annotations:
|
||||
|
||||
spec:
|
||||
publishNotReadyAddresses: true
|
||||
ports:
|
||||
- name: https
|
||||
port: 8200
|
||||
targetPort: 8200
|
||||
- name: https-internal
|
||||
port: 8201
|
||||
targetPort: 8201
|
||||
selector:
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
component: server
|
||||
vault-active: "false"
|
||||
---
|
||||
# Source: vault/templates/server-headless-service.yaml
|
||||
# Service for Vault cluster
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: vault-internal
|
||||
namespace: vault
|
||||
labels:
|
||||
helm.sh/chart: vault-0.19.0
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
annotations:
|
||||
|
||||
spec:
|
||||
clusterIP: None
|
||||
publishNotReadyAddresses: true
|
||||
ports:
|
||||
- name: "https"
|
||||
port: 8200
|
||||
targetPort: 8200
|
||||
- name: https-internal
|
||||
port: 8201
|
||||
targetPort: 8201
|
||||
selector:
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
component: server
|
||||
---
|
||||
# Source: vault/templates/server-service.yaml
|
||||
# Service for Vault cluster
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: vault
|
||||
namespace: vault
|
||||
labels:
|
||||
helm.sh/chart: vault-0.19.0
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
annotations:
|
||||
|
||||
spec:
|
||||
# We want the servers to become available even if they're not ready
|
||||
# since this DNS is also used for join operations.
|
||||
publishNotReadyAddresses: true
|
||||
ports:
|
||||
- name: https
|
||||
port: 8200
|
||||
targetPort: 8200
|
||||
- name: https-internal
|
||||
port: 8201
|
||||
targetPort: 8201
|
||||
selector:
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
component: server
|
||||
---
|
||||
# Source: vault/templates/ui-service.yaml
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: vault-ui
|
||||
namespace: vault
|
||||
labels:
|
||||
helm.sh/chart: vault-0.19.0
|
||||
app.kubernetes.io/name: vault-ui
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
component: server
|
||||
publishNotReadyAddresses: true
|
||||
ports:
|
||||
- name: https
|
||||
port: 8200
|
||||
targetPort: 8200
|
||||
type: ClusterIP
|
||||
---
|
||||
# Source: vault/templates/injector-deployment.yaml
|
||||
# Deployment for the injector
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: vault-agent-injector
|
||||
namespace: vault
|
||||
labels:
|
||||
app.kubernetes.io/name: vault-agent-injector
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
component: webhook
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: vault-agent-injector
|
||||
app.kubernetes.io/instance: vault
|
||||
component: webhook
|
||||
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: vault-agent-injector
|
||||
app.kubernetes.io/instance: vault
|
||||
component: webhook
|
||||
spec:
|
||||
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: vault-agent-injector
|
||||
app.kubernetes.io/instance: "vault"
|
||||
component: webhook
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
|
||||
|
||||
serviceAccountName: "vault-agent-injector"
|
||||
hostNetwork: false
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsGroup: 1000
|
||||
runAsUser: 100
|
||||
containers:
|
||||
- name: sidecar-injector
|
||||
resources:
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 50Mi
|
||||
|
||||
image: "hashicorp/vault-k8s:0.14.1"
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
env:
|
||||
- name: AGENT_INJECT_LISTEN
|
||||
value: :8080
|
||||
- name: AGENT_INJECT_LOG_LEVEL
|
||||
value: info
|
||||
- name: AGENT_INJECT_VAULT_ADDR
|
||||
value: https://vault.vault.svc:8200
|
||||
- name: AGENT_INJECT_VAULT_AUTH_PATH
|
||||
value: auth/kubernetes
|
||||
- name: AGENT_INJECT_VAULT_IMAGE
|
||||
value: "hashicorp/vault:1.9.2"
|
||||
- name: AGENT_INJECT_TLS_AUTO
|
||||
value: vault-agent-injector-cfg
|
||||
- name: AGENT_INJECT_TLS_AUTO_HOSTS
|
||||
value: vault-agent-injector-svc,vault-agent-injector-svc.vault,vault-agent-injector-svc.vault.svc
|
||||
- name: AGENT_INJECT_LOG_FORMAT
|
||||
value: standard
|
||||
- name: AGENT_INJECT_REVOKE_ON_SHUTDOWN
|
||||
value: "false"
|
||||
- name: AGENT_INJECT_CPU_REQUEST
|
||||
value: "250m"
|
||||
- name: AGENT_INJECT_CPU_LIMIT
|
||||
value: "500m"
|
||||
- name: AGENT_INJECT_MEM_REQUEST
|
||||
value: "64Mi"
|
||||
- name: AGENT_INJECT_MEM_LIMIT
|
||||
value: "128Mi"
|
||||
- name: AGENT_INJECT_DEFAULT_TEMPLATE
|
||||
value: "map"
|
||||
- name: AGENT_INJECT_TEMPLATE_CONFIG_EXIT_ON_RETRY_FAILURE
|
||||
value: "true"
|
||||
|
||||
- name: POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
args:
|
||||
- agent-inject
|
||||
- 2>&1
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 8080
|
||||
scheme: HTTPS
|
||||
failureThreshold: 2
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 2
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 8080
|
||||
scheme: HTTPS
|
||||
failureThreshold: 2
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 2
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 5
|
||||
---
|
||||
# Source: vault/templates/server-statefulset.yaml
|
||||
# StatefulSet to run the actual vault server cluster.
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: vault
|
||||
namespace: vault
|
||||
labels:
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
serviceName: vault-internal
|
||||
podManagementPolicy: Parallel
|
||||
replicas: 3
|
||||
updateStrategy:
|
||||
type: OnDelete
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
component: server
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
helm.sh/chart: vault-0.19.0
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: vault
|
||||
component: server
|
||||
spec:
|
||||
|
||||
affinity:
|
||||
podAntiAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
- labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: vault
|
||||
app.kubernetes.io/instance: "vault"
|
||||
component: server
|
||||
topologyKey: kubernetes.io/hostname
|
||||
|
||||
|
||||
|
||||
terminationGracePeriodSeconds: 10
|
||||
serviceAccountName: vault
|
||||
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsGroup: 1000
|
||||
runAsUser: 100
|
||||
fsGroup: 1000
|
||||
volumes:
|
||||
|
||||
- name: config
|
||||
configMap:
|
||||
name: vault-config
|
||||
|
||||
- name: userconfig-tls-server
|
||||
secret:
|
||||
secretName: tls-server
|
||||
defaultMode: 420
|
||||
- name: userconfig-tls-ca
|
||||
secret:
|
||||
secretName: tls-ca
|
||||
defaultMode: 420
|
||||
- name: home
|
||||
emptyDir: {}
|
||||
containers:
|
||||
- name: vault
|
||||
resources:
|
||||
limits:
|
||||
cpu: 2000m
|
||||
memory: 16Gi
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 50Mi
|
||||
|
||||
image: hashicorp/vault:1.9.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- "/bin/sh"
|
||||
- "-ec"
|
||||
args:
|
||||
- |
|
||||
cp /vault/config/extraconfig-from-values.hcl /tmp/storageconfig.hcl;
|
||||
[ -n "${HOST_IP}" ] && sed -Ei "s|HOST_IP|${HOST_IP?}|g" /tmp/storageconfig.hcl;
|
||||
[ -n "${POD_IP}" ] && sed -Ei "s|POD_IP|${POD_IP?}|g" /tmp/storageconfig.hcl;
|
||||
[ -n "${HOSTNAME}" ] && sed -Ei "s|HOSTNAME|${HOSTNAME?}|g" /tmp/storageconfig.hcl;
|
||||
[ -n "${API_ADDR}" ] && sed -Ei "s|API_ADDR|${API_ADDR?}|g" /tmp/storageconfig.hcl;
|
||||
[ -n "${TRANSIT_ADDR}" ] && sed -Ei "s|TRANSIT_ADDR|${TRANSIT_ADDR?}|g" /tmp/storageconfig.hcl;
|
||||
[ -n "${RAFT_ADDR}" ] && sed -Ei "s|RAFT_ADDR|${RAFT_ADDR?}|g" /tmp/storageconfig.hcl;
|
||||
/usr/local/bin/docker-entrypoint.sh vault server -config=/tmp/storageconfig.hcl
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
env:
|
||||
- name: HOST_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.hostIP
|
||||
- name: POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
- name: VAULT_K8S_POD_NAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: VAULT_K8S_NAMESPACE
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.namespace
|
||||
- name: VAULT_ADDR
|
||||
value: "https://127.0.0.1:8200"
|
||||
- name: VAULT_API_ADDR
|
||||
value: "https://$(POD_IP):8200"
|
||||
- name: SKIP_CHOWN
|
||||
value: "true"
|
||||
- name: SKIP_SETCAP
|
||||
value: "true"
|
||||
- name: HOSTNAME
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.name
|
||||
- name: VAULT_CLUSTER_ADDR
|
||||
value: "https://$(HOSTNAME).vault-internal:8201"
|
||||
- name: HOME
|
||||
value: "/home/vault"
|
||||
|
||||
|
||||
- name: "VAULT_CACERT"
|
||||
value: "/vault/userconfig/tls-ca/tls.crt"
|
||||
|
||||
volumeMounts:
|
||||
|
||||
|
||||
|
||||
- name: config
|
||||
mountPath: /vault/config
|
||||
|
||||
- name: userconfig-tls-server
|
||||
readOnly: true
|
||||
mountPath: /vault/userconfig/tls-server
|
||||
- name: userconfig-tls-ca
|
||||
readOnly: true
|
||||
mountPath: /vault/userconfig/tls-ca
|
||||
- name: home
|
||||
mountPath: /home/vault
|
||||
ports:
|
||||
- containerPort: 8200
|
||||
name: https
|
||||
- containerPort: 8201
|
||||
name: https-internal
|
||||
- containerPort: 8202
|
||||
name: https-rep
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: "/v1/sys/health?standbyok=true&sealedcode=204&uninitcode=204"
|
||||
port: 8200
|
||||
scheme: HTTPS
|
||||
failureThreshold: 2
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: "/v1/sys/health?standbyok=true"
|
||||
port: 8200
|
||||
scheme: HTTPS
|
||||
failureThreshold: 2
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 5
|
||||
successThreshold: 1
|
||||
timeoutSeconds: 3
|
||||
lifecycle:
|
||||
# Vault container doesn't receive SIGTERM from Kubernetes
|
||||
# and after the grace period ends, Kube sends SIGKILL. This
|
||||
# causes issues with graceful shutdowns such as deregistering itself
|
||||
# from Consul (zombie services).
|
||||
preStop:
|
||||
exec:
|
||||
command: [
|
||||
"/bin/sh", "-c",
|
||||
# Adding a sleep here to give the pod eviction a
|
||||
# chance to propagate, so requests will not be made
|
||||
# to this pod while it's terminating
|
||||
"sleep 5 && kill -SIGTERM $(pidof vault)",
|
||||
]
|
||||
|
||||
|
||||
volumeClaimTemplates:
|
||||
---
|
||||
# Source: vault/templates/injector-mutating-webhook.yaml
|
||||
apiVersion: admissionregistration.k8s.io/v1
|
||||
kind: MutatingWebhookConfiguration
|
||||
metadata:
|
||||
name: vault-agent-injector-cfg
|
||||
labels:
|
||||
app.kubernetes.io/name: vault-agent-injector
|
||||
app.kubernetes.io/instance: vault
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
webhooks:
|
||||
- name: vault.hashicorp.com
|
||||
sideEffects: None
|
||||
admissionReviewVersions:
|
||||
- "v1beta1"
|
||||
- "v1"
|
||||
clientConfig:
|
||||
service:
|
||||
name: vault-agent-injector-svc
|
||||
namespace: vault
|
||||
path: "/mutate"
|
||||
caBundle: ""
|
||||
rules:
|
||||
- operations: ["CREATE", "UPDATE"]
|
||||
apiGroups: [""]
|
||||
apiVersions: ["v1"]
|
||||
resources: ["pods"]
|
||||
failurePolicy: Ignore
|
||||
---
|
||||
# Source: vault/templates/tests/server-test.yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "vault-server-test"
|
||||
namespace: vault
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
spec:
|
||||
|
||||
containers:
|
||||
- name: vault-server-test
|
||||
image: hashicorp/vault:1.9.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: VAULT_ADDR
|
||||
value: https://vault.vault.svc:8200
|
||||
|
||||
- name: "VAULT_CACERT"
|
||||
value: "/vault/userconfig/tls-ca/tls.crt"
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
echo "Checking for sealed info in 'vault status' output"
|
||||
ATTEMPTS=10
|
||||
n=0
|
||||
until [ "$n" -ge $ATTEMPTS ]
|
||||
do
|
||||
echo "Attempt" $n...
|
||||
vault status -format yaml | grep -E '^sealed: (true|false)' && break
|
||||
n=$((n+1))
|
||||
sleep 5
|
||||
done
|
||||
if [ $n -ge $ATTEMPTS ]; then
|
||||
echo "timed out looking for sealed info in 'vault status' output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
volumeMounts:
|
||||
volumes:
|
||||
restartPolicy: Never
|
203
hashicorp/vault-2022/readme.md
Normal file
203
hashicorp/vault-2022/readme.md
Normal file
@ -0,0 +1,203 @@
|
||||
# Hashicorp Vault Guide
|
||||
|
||||
<a href="https://youtu.be/2Owo4Ioo9tQ" title="hashicorp-vault"><img src="https://i.ytimg.com/vi/2Owo4Ioo9tQ/hqdefault.jpg" width="20%" alt="introduction hashicorp vault" /></a>
|
||||
|
||||
Requirements:
|
||||
|
||||
* Kubernetes 1.21
|
||||
* Kind or Minikube
|
||||
|
||||
For this tutorial, I will be using Kubernetes 1.21.
|
||||
If you are watching the old guide for Kubernetes 1.17, go [here](..\vault\readme.md)
|
||||
|
||||
Lets create a Kubernetes cluster to play with using [kind](https://kind.sigs.k8s.io/docs/user/quick-start/)
|
||||
|
||||
```
|
||||
cd hashicorp/vault-2022
|
||||
|
||||
kind create cluster --name vault --image kindest/node:v1.21.1 --config kind.yaml
|
||||
```
|
||||
|
||||
Next up, I will be running a small container where I will be doing all the work from:
|
||||
You can skip this part if you already have `kubectl` and `helm` on your machine.
|
||||
|
||||
```
|
||||
docker run -it --rm --net host -v ${HOME}/.kube/:/root/.kube/ -v ${PWD}:/work -w /work alpine sh
|
||||
```
|
||||
|
||||
Install `kubectl`
|
||||
|
||||
```
|
||||
apk add --no-cache curl
|
||||
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
|
||||
chmod +x ./kubectl
|
||||
mv ./kubectl /usr/local/bin/kubectl
|
||||
```
|
||||
|
||||
Install `helm`
|
||||
|
||||
```
|
||||
curl -LO https://get.helm.sh/helm-v3.7.2-linux-amd64.tar.gz
|
||||
tar -C /tmp/ -zxvf helm-v3.7.2-linux-amd64.tar.gz
|
||||
rm helm-v3.7.2-linux-amd64.tar.gz
|
||||
mv /tmp/linux-amd64/helm /usr/local/bin/helm
|
||||
chmod +x /usr/local/bin/helm
|
||||
```
|
||||
|
||||
Now we have `helm` and `kubectl` and can access our `kind` cluster:
|
||||
|
||||
```
|
||||
kubectl get nodes
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
vault-control-plane Ready control-plane,master 37s v1.21.1
|
||||
```
|
||||
|
||||
Let's add the Helm repositories, so we can access the Kubernetes manifests
|
||||
|
||||
```
|
||||
helm repo add hashicorp https://helm.releases.hashicorp.com
|
||||
```
|
||||
|
||||
## Storage: Consul
|
||||
|
||||
We will use a very basic Consul cluster for our Vault backend. </br>
|
||||
Let's find what versions of Consul are available:
|
||||
|
||||
```
|
||||
helm search repo hashicorp/consul --versions
|
||||
```
|
||||
|
||||
We can use chart `0.39.0` which is the latest at the time of this demo
|
||||
Let's create a manifests folder and grab the YAML:
|
||||
|
||||
```
|
||||
|
||||
mkdir manifests
|
||||
|
||||
helm template consul hashicorp/consul \
|
||||
--namespace vault \
|
||||
--version 0.39.0 \
|
||||
-f consul-values.yaml \
|
||||
> ./manifests/consul.yaml
|
||||
```
|
||||
|
||||
Deploy the consul services:
|
||||
|
||||
```
|
||||
kubectl create ns vault
|
||||
kubectl -n vault apply -f ./manifests/consul.yaml
|
||||
kubectl -n vault get pods
|
||||
```
|
||||
|
||||
|
||||
## TLS End to End Encryption
|
||||
|
||||
See steps in [./tls/ssl_generate_self_signed.md](./tls/ssl_generate_self_signed.md)
|
||||
You'll need to generate TLS certs (or bring your own)
|
||||
Remember not to check-in your TLS to GIT :)
|
||||
|
||||
Create the TLS secret
|
||||
|
||||
```
|
||||
kubectl -n vault create secret tls tls-ca \
|
||||
--cert ./tls/ca.pem \
|
||||
--key ./tls/ca-key.pem
|
||||
|
||||
kubectl -n vault create secret tls tls-server \
|
||||
--cert ./tls/vault.pem \
|
||||
--key ./tls/vault-key.pem
|
||||
```
|
||||
|
||||
## Generate Kubernetes Manifests
|
||||
|
||||
|
||||
Let's find what versions of vault are available:
|
||||
|
||||
```
|
||||
helm search repo hashicorp/vault --versions
|
||||
```
|
||||
|
||||
In this demo I will use the `0.19.0` chart </br>
|
||||
|
||||
Let's firstly create a `values` file to customize vault.
|
||||
Let's grab the manifests:
|
||||
|
||||
```
|
||||
helm template vault hashicorp/vault \
|
||||
--namespace vault \
|
||||
--version 0.19.0 \
|
||||
-f vault-values.yaml \
|
||||
> ./manifests/vault.yaml
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
```
|
||||
kubectl -n vault apply -f ./manifests/vault.yaml
|
||||
kubectl -n vault get pods
|
||||
```
|
||||
|
||||
## Initialising Vault
|
||||
|
||||
```
|
||||
kubectl -n vault exec -it vault-0 -- sh
|
||||
kubectl -n vault exec -it vault-1 -- sh
|
||||
kubectl -n vault exec -it vault-2 -- sh
|
||||
|
||||
vault operator init
|
||||
vault operator unseal
|
||||
|
||||
kubectl -n vault exec -it vault-0 -- vault status
|
||||
kubectl -n vault exec -it vault-1 -- vault status
|
||||
kubectl -n vault exec -it vault-2 -- vault status
|
||||
|
||||
```
|
||||
## Web UI
|
||||
|
||||
Let's checkout the web UI:
|
||||
|
||||
```
|
||||
kubectl -n vault get svc
|
||||
kubectl -n vault port-forward svc/vault-ui 443:8200
|
||||
```
|
||||
Now we can access the web UI [here](https://localhost/)
|
||||
|
||||
## Enable Kubernetes Authentication
|
||||
|
||||
For the injector to be authorised to access vault, we need to enable K8s auth
|
||||
|
||||
```
|
||||
kubectl -n vault exec -it vault-0 -- sh
|
||||
|
||||
vault login
|
||||
vault auth enable kubernetes
|
||||
|
||||
vault write auth/kubernetes/config \
|
||||
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
|
||||
kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \
|
||||
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
|
||||
issuer="https://kubernetes.default.svc.cluster.local"
|
||||
exit
|
||||
```
|
||||
|
||||
# Summary
|
||||
|
||||
So we have a vault, an injector, TLS end to end, stateful storage.
|
||||
The injector can now inject secrets for pods from the vault.
|
||||
|
||||
Now we are ready to use the platform for different types of secrets:
|
||||
|
||||
## Secret Injection Guides
|
||||
|
||||
### Basic Secrets
|
||||
|
||||
Objective:
|
||||
----------
|
||||
* Let's create a basic secret in vault manually
|
||||
* Application consumes the secret automatically
|
||||
|
||||
[Try it](./example-apps/basic-secret/readme.md)
|
||||
|
||||
|
||||
|
||||
|
13
hashicorp/vault-2022/tls/ca-config.json
Normal file
13
hashicorp/vault-2022/tls/ca-config.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"signing": {
|
||||
"default": {
|
||||
"expiry": "175200h"
|
||||
},
|
||||
"profiles": {
|
||||
"default": {
|
||||
"usages": ["signing", "key encipherment", "server auth", "client auth"],
|
||||
"expiry": "175200h"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
hashicorp/vault-2022/tls/ca-csr.json
Normal file
18
hashicorp/vault-2022/tls/ca-csr.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"hosts": [
|
||||
"cluster.local"
|
||||
],
|
||||
"key": {
|
||||
"algo": "rsa",
|
||||
"size": 2048
|
||||
},
|
||||
"names": [
|
||||
{
|
||||
"C": "AU",
|
||||
"L": "Melbourne",
|
||||
"O": "Example",
|
||||
"OU": "CA",
|
||||
"ST": "Example"
|
||||
}
|
||||
]
|
||||
}
|
40
hashicorp/vault-2022/tls/ssl_generate_self_signed.md
Normal file
40
hashicorp/vault-2022/tls/ssl_generate_self_signed.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Use CFSSL to generate certificates
|
||||
|
||||
More about [CFSSL here]("https://github.com/cloudflare/cfssl")
|
||||
|
||||
```
|
||||
|
||||
cd hashicorp\vault-2022\tls
|
||||
|
||||
docker run -it --rm -v ${PWD}:/work -w /work debian bash
|
||||
|
||||
apt-get update && apt-get install -y curl &&
|
||||
curl -L https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssl_1.6.1_linux_amd64 -o /usr/local/bin/cfssl && \
|
||||
curl -L https://github.com/cloudflare/cfssl/releases/download/v1.6.1/cfssljson_1.6.1_linux_amd64 -o /usr/local/bin/cfssljson && \
|
||||
chmod +x /usr/local/bin/cfssl && \
|
||||
chmod +x /usr/local/bin/cfssljson
|
||||
|
||||
#generate ca in /tmp
|
||||
cfssl gencert -initca ca-csr.json | cfssljson -bare /tmp/ca
|
||||
|
||||
#generate certificate in /tmp
|
||||
cfssl gencert \
|
||||
-ca=/tmp/ca.pem \
|
||||
-ca-key=/tmp/ca-key.pem \
|
||||
-config=ca-config.json \
|
||||
-hostname="vault,vault.vault.svc.cluster.local,vault.vault.svc,localhost,127.0.0.1" \
|
||||
-profile=default \
|
||||
ca-csr.json | cfssljson -bare /tmp/vault
|
||||
```
|
||||
|
||||
view the files:
|
||||
|
||||
```
|
||||
ls -l /tmp
|
||||
```
|
||||
|
||||
access the files:
|
||||
|
||||
```
|
||||
mv /tmp/* .
|
||||
```
|
85
hashicorp/vault-2022/vault-values.yaml
Normal file
85
hashicorp/vault-2022/vault-values.yaml
Normal file
@ -0,0 +1,85 @@
|
||||
# Vault Helm Chart Value Overrides
|
||||
global:
|
||||
enabled: true
|
||||
tlsDisable: false
|
||||
|
||||
injector:
|
||||
enabled: true
|
||||
# Use the Vault K8s Image https://github.com/hashicorp/vault-k8s/
|
||||
image:
|
||||
repository: "hashicorp/vault-k8s"
|
||||
tag: "0.14.1"
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: 50Mi
|
||||
cpu: 50m
|
||||
limits:
|
||||
memory: 256Mi
|
||||
cpu: 250m
|
||||
|
||||
server:
|
||||
image:
|
||||
repository: "hashicorp/vault"
|
||||
tag: "1.9.2"
|
||||
|
||||
# These Resource Limits are in line with node requirements in the
|
||||
# Vault Reference Architecture for a Small Cluster
|
||||
resources:
|
||||
requests:
|
||||
memory: 50Mi
|
||||
cpu: 500m
|
||||
limits:
|
||||
memory: 16Gi
|
||||
cpu: 2000m
|
||||
|
||||
# For HA configuration and because we need to manually init the vault,
|
||||
# we need to define custom readiness/liveness Probe settings
|
||||
readinessProbe:
|
||||
enabled: true
|
||||
path: "/v1/sys/health?standbyok=true&sealedcode=204&uninitcode=204"
|
||||
livenessProbe:
|
||||
enabled: true
|
||||
path: "/v1/sys/health?standbyok=true"
|
||||
initialDelaySeconds: 60
|
||||
|
||||
# extraEnvironmentVars is a list of extra environment variables to set with the stateful set. These could be
|
||||
# used to include variables required for auto-unseal.
|
||||
extraEnvironmentVars:
|
||||
VAULT_CACERT: /vault/userconfig/tls-ca/tls.crt
|
||||
|
||||
# extraVolumes is a list of extra volumes to mount. These will be exposed
|
||||
# to Vault in the path `/vault/userconfig/<name>/`.
|
||||
extraVolumes:
|
||||
- type: secret
|
||||
name: tls-server
|
||||
- type: secret
|
||||
name: tls-ca
|
||||
|
||||
standalone:
|
||||
enabled: false
|
||||
|
||||
# Run Vault in "HA" mode.
|
||||
ha:
|
||||
enabled: true
|
||||
replicas: 3
|
||||
config: |
|
||||
ui = true
|
||||
|
||||
listener "tcp" {
|
||||
tls_disable = 0
|
||||
address = "0.0.0.0:8200"
|
||||
tls_cert_file = "/vault/userconfig/tls-server/tls.crt"
|
||||
tls_key_file = "/vault/userconfig/tls-server/tls.key"
|
||||
tls_min_version = "tls12"
|
||||
}
|
||||
|
||||
storage "consul" {
|
||||
path = "vault"
|
||||
address = "consul-consul-server:8500"
|
||||
}
|
||||
|
||||
# Vault UI
|
||||
ui:
|
||||
enabled: true
|
||||
externalPort: 8200
|
39
hashicorp/vault/example-apps/basic-secret/deployment.yaml
Normal file
39
hashicorp/vault/example-apps/basic-secret/deployment.yaml
Normal file
@ -0,0 +1,39 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: basic-secret
|
||||
labels:
|
||||
app: basic-secret
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: basic-secret
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
vault.hashicorp.com/tls-skip-verify: "true"
|
||||
vault.hashicorp.com/agent-inject-secret-helloworld: "secret/basic-secret/helloworld"
|
||||
vault.hashicorp.com/agent-inject-template-helloworld: |
|
||||
{{- with secret "secret/basic-secret/helloworld" -}}
|
||||
{
|
||||
"username" : "{{ .Data.username }}",
|
||||
"password" : "{{ .Data.password }}"
|
||||
}
|
||||
{{- end }}
|
||||
vault.hashicorp.com/role: "basic-secret-role"
|
||||
labels:
|
||||
app: basic-secret
|
||||
spec:
|
||||
serviceAccountName: basic-secret
|
||||
containers:
|
||||
- name: app
|
||||
image: jweissig/app:0.0.1
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: basic-secret
|
||||
labels:
|
||||
app: basic-secret
|
50
hashicorp/vault/example-apps/basic-secret/readme.md
Normal file
50
hashicorp/vault/example-apps/basic-secret/readme.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Basic Secret Injection
|
||||
|
||||
|
||||
In order for us to start using secrets in vault, we need to setup a policy.
|
||||
|
||||
|
||||
```
|
||||
#Create a role for our app
|
||||
|
||||
kubectl -n vault-example exec -it vault-example-0 sh
|
||||
|
||||
vault write auth/kubernetes/role/basic-secret-role \
|
||||
bound_service_account_names=basic-secret \
|
||||
bound_service_account_namespaces=vault-example \
|
||||
policies=basic-secret-policy \
|
||||
ttl=1h
|
||||
```
|
||||
|
||||
The above maps our Kubernetes service account, used by our pod, to a policy.
|
||||
Now lets create the policy to map our service account to a bunch of secrets
|
||||
|
||||
|
||||
```
|
||||
kubectl -n vault-example exec -it vault-example-0 sh
|
||||
cat <<EOF > /home/vault/app-policy.hcl
|
||||
path "secret/basic-secret/*" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
EOF
|
||||
vault policy write basic-secret-policy /home/vault/app-policy.hcl
|
||||
exit
|
||||
```
|
||||
|
||||
Now our service account for our pod can access all secrets under `secret/basic-secret/*`
|
||||
Lets create some secrets.
|
||||
|
||||
|
||||
```
|
||||
kubectl -n vault-example exec -it vault-example-0 sh
|
||||
vault secrets enable -path=secret/ kv
|
||||
vault kv put secret/basic-secret/helloworld username=dbuser password=sUp3rS3cUr3P@ssw0rd
|
||||
exit
|
||||
```
|
||||
|
||||
Lets deploy our app and see if it works:
|
||||
|
||||
```
|
||||
kubectl -n vault-example apply -f ./hashicorp/vault/example-apps/basic-secret/deployment.yaml
|
||||
kubectl -n vault-example get pods
|
||||
```
|
@ -0,0 +1,38 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dynamic-postgres
|
||||
labels:
|
||||
app: dynamic-postgres
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: dynamic-postgres
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
vault.hashicorp.com/agent-inject: "true"
|
||||
vault.hashicorp.com/tls-skip-verify: "true"
|
||||
vault.hashicorp.com/agent-inject-secret-sql-role: "database/creds/sql-role"
|
||||
vault.hashicorp.com/agent-inject-template-sql-role: |
|
||||
{
|
||||
{{- with secret "database/creds/sql-role" -}}
|
||||
"db_connection": "host=postgres.postgress port=5432 user={{ .Data.username }} password={{ .Data.password }} dbname=postgresdb sslmode=disable"
|
||||
{{- end }}
|
||||
}
|
||||
vault.hashicorp.com/role: "sql-role"
|
||||
labels:
|
||||
app: dynamic-postgres
|
||||
spec:
|
||||
serviceAccountName: dynamic-postgres
|
||||
containers:
|
||||
- name: app
|
||||
image: jweissig/app:0.0.1
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: dynamic-postgres
|
||||
labels:
|
||||
app: dynamic-postgres
|
33
hashicorp/vault/example-apps/dynamic-postgresql/pgadmin.yaml
Normal file
33
hashicorp/vault/example-apps/dynamic-postgresql/pgadmin.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: pgadmin-config
|
||||
labels:
|
||||
app: pgadmin
|
||||
data:
|
||||
PGADMIN_DEFAULT_EMAIL: admin@admin.com
|
||||
PGADMIN_DEFAULT_PASSWORD: admin123
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: pgadmin
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: pgadmin
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: pgadmin
|
||||
spec:
|
||||
containers:
|
||||
- name: pgadmin
|
||||
image: dpage/pgadmin4
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
ports:
|
||||
- containerPort: 80
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: pgadmin-config
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user