├── Dockerfile ├── Pipfile ├── Pipfile.lock ├── README.md ├── crd.yml ├── exchangerate.yml ├── exchangerates-operator.py └── exchangerates-operator.yml /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8-alpine 2 | RUN apk --update add gcc build-base 3 | RUN pip install --no-cache-dir kopf kubernetes requests 4 | ADD exchangerates-operator.py / 5 | CMD kopf run /exchangerates-operator.py -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | kopf = "*" 8 | kubernetes = "*" 9 | requests = "*" 10 | 11 | [dev-packages] 12 | 13 | [requires] 14 | python_version = "3.8" 15 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "8a2dcb4c50fd94a00ccdf559ec1cd21e22c7ad008eba8590b18fbd8cb528260a" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "aiohttp": { 20 | "hashes": [ 21 | "sha256:0b795072bb1bf87b8620120a6373a3c61bfcb8da7e5c2377f4bb23ff4f0b62c9", 22 | "sha256:0d438c8ca703b1b714e82ed5b7a4412c82577040dadff479c08405e2a715564f", 23 | "sha256:16a3cb5df5c56f696234ea9e65e227d1ebe9c18aa774d36ff42f532139066a5f", 24 | "sha256:1edfd82a98c5161497bbb111b2b70c0813102ad7e0aa81cbeb34e64c93863005", 25 | "sha256:2406dc1dda01c7f6060ab586e4601f18affb7a6b965c50a8c90ff07569cf782a", 26 | "sha256:2858b2504c8697beb9357be01dc47ef86438cc1cb36ecb6991796d19475faa3e", 27 | "sha256:2a7b7640167ab536c3cb90cfc3977c7094f1c5890d7eeede8b273c175c3910fd", 28 | "sha256:3228b7a51e3ed533f5472f54f70fd0b0a64c48dc1649a0f0e809bec312934d7a", 29 | "sha256:328b552513d4f95b0a2eea4c8573e112866107227661834652a8984766aa7656", 30 | "sha256:39f4b0a6ae22a1c567cb0630c30dd082481f95c13ca528dc501a7766b9c718c0", 31 | "sha256:3b0036c978cbcc4a4512278e98e3e6d9e6b834dc973206162eddf98b586ef1c6", 32 | "sha256:3ea8c252d8df5e9166bcf3d9edced2af132f4ead8ac422eac723c5781063709a", 33 | "sha256:41608c0acbe0899c852281978492f9ce2c6fbfaf60aff0cefc54a7c4516b822c", 34 | "sha256:59d11674964b74a81b149d4ceaff2b674b3b0e4d0f10f0be1533e49c4a28408b", 35 | "sha256:5e479df4b2d0f8f02133b7e4430098699450e1b2a826438af6bec9a400530957", 36 | "sha256:684850fb1e3e55c9220aad007f8386d8e3e477c4ec9211ae54d968ecdca8c6f9", 37 | "sha256:6ccc43d68b81c424e46192a778f97da94ee0630337c9bbe5b2ecc9b0c1c59001", 38 | "sha256:6d42debaf55450643146fabe4b6817bb2a55b23698b0434107e892a43117285e", 39 | "sha256:710376bf67d8ff4500a31d0c207b8941ff4fba5de6890a701d71680474fe2a60", 40 | "sha256:756ae7efddd68d4ea7d89c636b703e14a0c686688d42f588b90778a3c2fc0564", 41 | "sha256:77149002d9386fae303a4a162e6bce75cc2161347ad2ba06c2f0182561875d45", 42 | "sha256:78e2f18a82b88cbc37d22365cf8d2b879a492faedb3f2975adb4ed8dfe994d3a", 43 | "sha256:7d9b42127a6c0bdcc25c3dcf252bb3ddc70454fac593b1b6933ae091396deb13", 44 | "sha256:8389d6044ee4e2037dca83e3f6994738550f6ee8cfb746762283fad9b932868f", 45 | "sha256:9c1a81af067e72261c9cbe33ea792893e83bc6aa987bfbd6fdc1e5e7b22777c4", 46 | "sha256:c1e0920909d916d3375c7a1fdb0b1c78e46170e8bb42792312b6eb6676b2f87f", 47 | "sha256:c68fdf21c6f3573ae19c7ee65f9ff185649a060c9a06535e9c3a0ee0bbac9235", 48 | "sha256:c733ef3bdcfe52a1a75564389bad4064352274036e7e234730526d155f04d914", 49 | "sha256:c9c58b0b84055d8bc27b7df5a9d141df4ee6ff59821f922dd73155861282f6a3", 50 | "sha256:d03abec50df423b026a5aa09656bd9d37f1e6a49271f123f31f9b8aed5dc3ea3", 51 | "sha256:d2cfac21e31e841d60dc28c0ec7d4ec47a35c608cb8906435d47ef83ffb22150", 52 | "sha256:dcc119db14757b0c7bce64042158307b9b1c76471e655751a61b57f5a0e4d78e", 53 | "sha256:df3a7b258cc230a65245167a202dd07320a5af05f3d41da1488ba0fa05bc9347", 54 | "sha256:df48a623c58180874d7407b4d9ec06a19b84ed47f60a3884345b1a5099c1818b", 55 | "sha256:e1b95972a0ae3f248a899cdbac92ba2e01d731225f566569311043ce2226f5e7", 56 | "sha256:f326b3c1bbfda5b9308252ee0dcb30b612ee92b0e105d4abec70335fab5b1245", 57 | "sha256:f411cb22115cb15452d099fec0ee636b06cf81bfb40ed9c02d30c8dc2bc2e3d1" 58 | ], 59 | "markers": "python_version >= '3.6'", 60 | "version": "==3.7.3" 61 | }, 62 | "aiojobs": { 63 | "hashes": [ 64 | "sha256:8e4b3e3d1bdb970bdaf8f8cd5eb4e4ff3e0e01a4abd22b4f73a87002a5ae4005", 65 | "sha256:bf93b4a8d5a3ade3f85b6edb6efd9703acf60a8c5492b9fc4a6df843dd8decb3" 66 | ], 67 | "markers": "python_version >= '3.5'", 68 | "version": "==0.2.2" 69 | }, 70 | "async-timeout": { 71 | "hashes": [ 72 | "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", 73 | "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" 74 | ], 75 | "markers": "python_full_version >= '3.5.3'", 76 | "version": "==3.0.1" 77 | }, 78 | "attrs": { 79 | "hashes": [ 80 | "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", 81 | "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" 82 | ], 83 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 84 | "version": "==20.3.0" 85 | }, 86 | "cachetools": { 87 | "hashes": [ 88 | "sha256:513d4ff98dd27f85743a8dc0e92f55ddb1b49e060c2d5961512855cda2c01a98", 89 | "sha256:bbaa39c3dede00175df2dc2b03d0cf18dd2d32a7de7beb68072d13043c9edb20" 90 | ], 91 | "markers": "python_version ~= '3.5'", 92 | "version": "==4.1.1" 93 | }, 94 | "certifi": { 95 | "hashes": [ 96 | "sha256:1f422849db327d534e3d0c5f02a263458c3955ec0aae4ff09b95f195c59f4edd", 97 | "sha256:f05def092c44fbf25834a51509ef6e631dc19765ab8a57b4e7ab85531f0a9cf4" 98 | ], 99 | "version": "==2020.11.8" 100 | }, 101 | "chardet": { 102 | "hashes": [ 103 | "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", 104 | "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" 105 | ], 106 | "version": "==3.0.4" 107 | }, 108 | "click": { 109 | "hashes": [ 110 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 111 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 112 | ], 113 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 114 | "version": "==7.1.2" 115 | }, 116 | "google-auth": { 117 | "hashes": [ 118 | "sha256:5176db85f1e7e837a646cd9cede72c3c404ccf2e3373d9ee14b2db88febad440", 119 | "sha256:b728625ff5dfce8f9e56a499c8a4eb51443a67f20f6d28b67d5774c310ec4b6b" 120 | ], 121 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 122 | "version": "==1.23.0" 123 | }, 124 | "idna": { 125 | "hashes": [ 126 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", 127 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" 128 | ], 129 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 130 | "version": "==2.10" 131 | }, 132 | "iso8601": { 133 | "hashes": [ 134 | "sha256:694be0743e9f1535ea873bfc7bd6fb62380c62b75822761859428073a17fd39c", 135 | "sha256:6f02f01dd13320a7f280e58516dc8d1950dfaf77527cc365a398cd9de4d3c692", 136 | "sha256:f7dec22af52025d4526be94cc1303c7d8f5379b746a3f54a8c8446384392eeb1" 137 | ], 138 | "version": "==0.1.13" 139 | }, 140 | "kopf": { 141 | "hashes": [ 142 | "sha256:8a11acb46f61d658b2a19886edfc6682fc35511cb55da18516df3797a6e27a15", 143 | "sha256:c84c2d394e00550f2783da43513756973c8b718b356691bc7fb18f37b431fbdf" 144 | ], 145 | "index": "pypi", 146 | "version": "==0.28" 147 | }, 148 | "kubernetes": { 149 | "hashes": [ 150 | "sha256:23c85d8571df8f56e773f1a413bc081537536dc47e2b5e8dc2e6262edb2c57ca", 151 | "sha256:ec52ea01d52e2ec3da255992f7e859f3a76f2bdb51cf65ba8cd71dfc309d8daa" 152 | ], 153 | "index": "pypi", 154 | "version": "==12.0.1" 155 | }, 156 | "multidict": { 157 | "hashes": [ 158 | "sha256:060d68ae3e674c913ec41a464916f12c4d7ff17a3a9ebbf37ba7f2c681c2b33e", 159 | "sha256:06f39f0ddc308dab4e5fa282d145f90cd38d7ed75390fc83335636909a9ec191", 160 | "sha256:17847fede1aafdb7e74e01bb34ab47a1a1ea726e8184c623c45d7e428d2d5d34", 161 | "sha256:1cd102057b09223b919f9447c669cf2efabeefb42a42ae6233f25ffd7ee31a79", 162 | "sha256:20cc9b2dd31761990abff7d0e63cd14dbfca4ebb52a77afc917b603473951a38", 163 | "sha256:2576e30bbec004e863d87216bc34abe24962cc2e964613241a1c01c7681092ab", 164 | "sha256:2ab9cad4c5ef5c41e1123ed1f89f555aabefb9391d4e01fd6182de970b7267ed", 165 | "sha256:359ea00e1b53ceef282232308da9d9a3f60d645868a97f64df19485c7f9ef628", 166 | "sha256:3e61cc244fd30bd9fdfae13bdd0c5ec65da51a86575ff1191255cae677045ffe", 167 | "sha256:43c7a87d8c31913311a1ab24b138254a0ee89142983b327a2c2eab7a7d10fea9", 168 | "sha256:4a3f19da871befa53b48dd81ee48542f519beffa13090dc135fffc18d8fe36db", 169 | "sha256:4df708ef412fd9b59b7e6c77857e64c1f6b4c0116b751cb399384ec9a28baa66", 170 | "sha256:59182e975b8c197d0146a003d0f0d5dc5487ce4899502061d8df585b0f51fba2", 171 | "sha256:6128d2c0956fd60e39ec7d1c8f79426f0c915d36458df59ddd1f0cff0340305f", 172 | "sha256:6168839491a533fa75f3f5d48acbb829475e6c7d9fa5c6e245153b5f79b986a3", 173 | "sha256:62abab8088704121297d39c8f47156cb8fab1da731f513e59ba73946b22cf3d0", 174 | "sha256:653b2bbb0bbf282c37279dd04f429947ac92713049e1efc615f68d4e64b1dbc2", 175 | "sha256:6566749cd78cb37cbf8e8171b5cd2cbfc03c99f0891de12255cf17a11c07b1a3", 176 | "sha256:76cbdb22f48de64811f9ce1dd4dee09665f84f32d6a26de249a50c1e90e244e0", 177 | "sha256:8efcf070d60fd497db771429b1c769a3783e3a0dd96c78c027e676990176adc5", 178 | "sha256:8fa4549f341a057feec4c3139056ba73e17ed03a506469f447797a51f85081b5", 179 | "sha256:9380b3f2b00b23a4106ba9dd022df3e6e2e84e1788acdbdd27603b621b3288df", 180 | "sha256:9ed9b280f7778ad6f71826b38a73c2fdca4077817c64bc1102fdada58e75c03c", 181 | "sha256:a7b8b5bd16376c8ac2977748bd978a200326af5145d8d0e7f799e2b355d425b6", 182 | "sha256:af271c2540d1cd2a137bef8d95a8052230aa1cda26dd3b2c73d858d89993d518", 183 | "sha256:b561e76c9e21402d9a446cdae13398f9942388b9bff529f32dfa46220af54d00", 184 | "sha256:b82400ef848bbac6b9035a105ac6acaa1fb3eea0d164e35bbb21619b88e49fed", 185 | "sha256:b98af08d7bb37d3456a22f689819ea793e8d6961b9629322d7728c4039071641", 186 | "sha256:c58e53e1c73109fdf4b759db9f2939325f510a8a5215135330fe6755921e4886", 187 | "sha256:cbabfc12b401d074298bfda099c58dfa5348415ae2e4ec841290627cb7cb6b2e", 188 | "sha256:d4a6fb98e9e9be3f7d70fd3e852369c00a027bd5ed0f3e8ade3821bcad257408", 189 | "sha256:d99da85d6890267292065e654a329e1d2f483a5d2485e347383800e616a8c0b1", 190 | "sha256:e58db0e0d60029915f7fc95a8683fa815e204f2e1990f1fb46a7778d57ca8c35", 191 | "sha256:e5bf89fe57f702a046c7ec718fe330ed50efd4bcf74722940db2eb0919cddb1c", 192 | "sha256:f612e8ef8408391a4a3366e3508bab8ef97b063b4918a317cb6e6de4415f01af", 193 | "sha256:f65a2442c113afde52fb09f9a6276bbc31da71add99dc76c3adf6083234e07c6", 194 | "sha256:fa0503947a99a1be94f799fac89d67a5e20c333e78ddae16e8534b151cdc588a" 195 | ], 196 | "markers": "python_version >= '3.5'", 197 | "version": "==5.0.2" 198 | }, 199 | "oauthlib": { 200 | "hashes": [ 201 | "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889", 202 | "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea" 203 | ], 204 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 205 | "version": "==3.1.0" 206 | }, 207 | "pyasn1": { 208 | "hashes": [ 209 | "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", 210 | "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", 211 | "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", 212 | "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", 213 | "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", 214 | "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", 215 | "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", 216 | "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", 217 | "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", 218 | "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", 219 | "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", 220 | "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", 221 | "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" 222 | ], 223 | "version": "==0.4.8" 224 | }, 225 | "pyasn1-modules": { 226 | "hashes": [ 227 | "sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8", 228 | "sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199", 229 | "sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811", 230 | "sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed", 231 | "sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4", 232 | "sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e", 233 | "sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74", 234 | "sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb", 235 | "sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45", 236 | "sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd", 237 | "sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0", 238 | "sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d", 239 | "sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405" 240 | ], 241 | "version": "==0.2.8" 242 | }, 243 | "pykube-ng": { 244 | "hashes": [ 245 | "sha256:72c5fbff0da32ec1682927167742f4ebf7b33a1ad01035511d5fec2de208220f", 246 | "sha256:d6a5f9101719d461db1f7e11cfdde4c1f8bae5c6a89f4c08e5008aae9327b338" 247 | ], 248 | "markers": "python_version >= '3.6'", 249 | "version": "==20.10.0" 250 | }, 251 | "python-dateutil": { 252 | "hashes": [ 253 | "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", 254 | "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" 255 | ], 256 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 257 | "version": "==2.8.1" 258 | }, 259 | "python-json-logger": { 260 | "hashes": [ 261 | "sha256:f26eea7898db40609563bed0a7ca11af12e2a79858632706d835a0f961b7d398" 262 | ], 263 | "markers": "python_version >= '3.4'", 264 | "version": "==2.0.1" 265 | }, 266 | "pyyaml": { 267 | "hashes": [ 268 | "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", 269 | "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", 270 | "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", 271 | "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e", 272 | "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", 273 | "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", 274 | "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", 275 | "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", 276 | "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", 277 | "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a", 278 | "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", 279 | "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", 280 | "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" 281 | ], 282 | "version": "==5.3.1" 283 | }, 284 | "requests": { 285 | "hashes": [ 286 | "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8", 287 | "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998" 288 | ], 289 | "index": "pypi", 290 | "version": "==2.25.0" 291 | }, 292 | "requests-oauthlib": { 293 | "hashes": [ 294 | "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d", 295 | "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a", 296 | "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc" 297 | ], 298 | "version": "==1.3.0" 299 | }, 300 | "rsa": { 301 | "hashes": [ 302 | "sha256:109ea5a66744dd859bf16fe904b8d8b627adafb9408753161e766a92e7d681fa", 303 | "sha256:6166864e23d6b5195a5cfed6cd9fed0fe774e226d8f854fcb23b7bbef0350233" 304 | ], 305 | "markers": "python_version >= '3.5'", 306 | "version": "==4.6" 307 | }, 308 | "six": { 309 | "hashes": [ 310 | "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", 311 | "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" 312 | ], 313 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 314 | "version": "==1.15.0" 315 | }, 316 | "typing-extensions": { 317 | "hashes": [ 318 | "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918", 319 | "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c", 320 | "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f" 321 | ], 322 | "version": "==3.7.4.3" 323 | }, 324 | "urllib3": { 325 | "hashes": [ 326 | "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08", 327 | "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" 328 | ], 329 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", 330 | "version": "==1.26.2" 331 | }, 332 | "websocket-client": { 333 | "hashes": [ 334 | "sha256:0fc45c961324d79c781bab301359d5a1b00b13ad1b10415a4780229ef71a5549", 335 | "sha256:d735b91d6d1692a6a181f2a8c9e0238e5f6373356f561bb9dc4c7af36f452010" 336 | ], 337 | "version": "==0.57.0" 338 | }, 339 | "yarl": { 340 | "hashes": [ 341 | "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e", 342 | "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434", 343 | "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366", 344 | "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3", 345 | "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec", 346 | "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959", 347 | "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e", 348 | "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c", 349 | "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6", 350 | "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a", 351 | "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6", 352 | "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424", 353 | "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e", 354 | "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f", 355 | "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50", 356 | "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2", 357 | "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc", 358 | "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4", 359 | "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970", 360 | "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10", 361 | "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0", 362 | "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406", 363 | "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896", 364 | "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643", 365 | "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721", 366 | "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478", 367 | "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724", 368 | "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e", 369 | "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8", 370 | "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96", 371 | "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25", 372 | "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76", 373 | "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2", 374 | "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2", 375 | "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c", 376 | "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a", 377 | "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71" 378 | ], 379 | "markers": "python_version >= '3.6'", 380 | "version": "==1.6.3" 381 | } 382 | }, 383 | "develop": {} 384 | } 385 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Further info: 2 | - https://brennerm.github.io/posts/k8s-operators-with-python-part-1.html 3 | - https://brennerm.github.io/posts/k8s-operators-with-python-part-2.html 4 | -------------------------------------------------------------------------------- /crd.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: exchangerates.operators.brennerm.github.io 5 | spec: 6 | group: operators.brennerm.github.io 7 | versions: 8 | - name: v1 # it's possible to provide multiple versions of a CRD 9 | served: true # it's possible to disable a CRD 10 | storage: true 11 | schema: 12 | openAPIV3Schema: 13 | type: object 14 | properties: 15 | spec: 16 | type: object 17 | properties: 18 | currency: 19 | type: string 20 | enum: ["CAD","CHF","GBP","JPY","PLN","USD"] # we'll limit the valid currencies to these 21 | status: 22 | type: object 23 | x-kubernetes-preserve-unknown-fields: true 24 | scope: Namespaced # resources can be namespaced or available for the whole cluster 25 | names: 26 | plural: exchangerates 27 | singular: exchangerate 28 | kind: ExchangeRate # this name is being used in manifests 29 | shortNames: # these short names can be used in the CLI, e.g. kubectl get er 30 | - er -------------------------------------------------------------------------------- /exchangerate.yml: -------------------------------------------------------------------------------- 1 | apiVersion: operators.brennerm.github.io/v1 2 | kind: ExchangeRate 3 | metadata: 4 | name: exchange-rate-usd 5 | spec: 6 | currency: USD 7 | -------------------------------------------------------------------------------- /exchangerates-operator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import kopf 4 | import kubernetes.config as k8s_config 5 | import kubernetes.client as k8s_client 6 | import requests 7 | 8 | 9 | exchange_rate_crd = k8s_client.V1CustomResourceDefinition( 10 | api_version="apiextensions.k8s.io/v1", 11 | kind="CustomResourceDefinition", 12 | metadata=k8s_client.V1ObjectMeta(name="exchangerates.operators.brennerm.github.io"), 13 | spec=k8s_client.V1CustomResourceDefinitionSpec( 14 | group="operators.brennerm.github.io", 15 | versions=[k8s_client.V1CustomResourceDefinitionVersion( 16 | name="v1", 17 | served=True, 18 | storage=True, 19 | schema=k8s_client.V1CustomResourceValidation( 20 | open_apiv3_schema=k8s_client.V1JSONSchemaProps( 21 | type="object", 22 | properties={ 23 | "spec": k8s_client.V1JSONSchemaProps( 24 | type="object", 25 | properties={"currency": k8s_client.V1JSONSchemaProps( 26 | type="string", 27 | enum=["CAD","CHF","GBP","JPY","PLN","USD"] 28 | )} 29 | ), 30 | "status": k8s_client.V1JSONSchemaProps( 31 | type="object", 32 | x_kubernetes_preserve_unknown_fields=True 33 | ) 34 | } 35 | ) 36 | ) 37 | )], 38 | scope="Namespaced", 39 | names=k8s_client.V1CustomResourceDefinitionNames( 40 | plural="exchangerates", 41 | singular="exchangerate", 42 | kind="ExchangeRate", 43 | short_names=["er"] 44 | ) 45 | ) 46 | ) 47 | 48 | try: 49 | k8s_config.load_kube_config() 50 | except k8s_config.ConfigException: 51 | k8s_config.load_incluster_config() 52 | 53 | api_instance = k8s_client.ApiextensionsV1Api() 54 | try: 55 | api_instance.create_custom_resource_definition(exchange_rate_crd) 56 | except k8s_client.rest.ApiException as e: 57 | if e.status == 409: 58 | print("CRD already exists") 59 | else: 60 | raise e 61 | 62 | def get_exchange_rate(currency): 63 | exchange_rates_url = 'https://api.exchangeratesapi.io/latest?symbols=' 64 | return requests.get(f"{exchange_rates_url}{currency}").json()['rates'][currency] 65 | 66 | def create_exchange_rate_config_map(namespace, data): 67 | api_instance = k8s_client.CoreV1Api() 68 | return api_instance.create_namespaced_config_map(namespace, data) 69 | 70 | 71 | def update_exchange_rate_config_map(namespace, name, new_data): 72 | api_instance = k8s_client.CoreV1Api() 73 | return api_instance.patch_namespaced_config_map(name, namespace, new_data) 74 | 75 | @kopf.on.create('operators.brennerm.github.io', 'v1', 'exchangerates') 76 | def on_create(namespace, spec, body, **kwargs): 77 | currency = spec['currency'] 78 | rate = get_exchange_rate(currency) 79 | data = __rate_to_config_map_data(rate) 80 | 81 | kopf.adopt(data) 82 | 83 | configmap = create_exchange_rate_config_map(namespace, data) 84 | return {'configmap-name': configmap.metadata.name} 85 | 86 | 87 | @kopf.on.update('operators.brennerm.github.io', 'v1', 'exchangerates') 88 | def on_update(namespace, name, spec, status, **kwargs): 89 | currency = spec['currency'] 90 | rate = get_exchange_rate(currency) 91 | name = status['on_create']['configmap-name'] 92 | data = __rate_to_config_map_data(rate) 93 | 94 | update_exchange_rate_config_map(namespace, name, data) 95 | 96 | def __rate_to_config_map_data(rate): 97 | return { 98 | 'data': { 99 | 'rate': str(rate) 100 | } 101 | } -------------------------------------------------------------------------------- /exchangerates-operator.yml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: exchangerates-operator 5 | 6 | --- 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | metadata: 10 | name: exchangerates-operator 11 | labels: 12 | app: exchangerates-operator 13 | spec: 14 | replicas: 1 # make sure to not have more than one replicas 15 | strategy: 16 | type: Recreate # make sure the old pod is being killed before the new pod is being created 17 | selector: 18 | matchLabels: 19 | app: exchangerates-operator 20 | template: 21 | metadata: 22 | labels: 23 | app: exchangerates-operator 24 | spec: 25 | serviceAccountName: exchangerates-operator 26 | containers: 27 | - name: exchangerates-operator 28 | image: exchangerates-operator:v1 29 | imagePullPolicy: IfNotPresent 30 | 31 | --- 32 | 33 | apiVersion: rbac.authorization.k8s.io/v1 34 | kind: ClusterRole 35 | metadata: 36 | name: exchangerates-operator 37 | rules: 38 | - apiGroups: ["apiextensions.k8s.io"] 39 | resources: ["customresourcedefinitions"] 40 | verbs: ["create"] 41 | - apiGroups: [""] 42 | resources: ["configmaps"] 43 | verbs: ["create, patch"] 44 | - apiGroups: [""] 45 | resources: ["events"] 46 | verbs: ["create"] 47 | - apiGroups: ["operators.brennerm.github.io"] 48 | resources: ["exchangerates"] 49 | verbs: ["*"] 50 | --- 51 | 52 | apiVersion: rbac.authorization.k8s.io/v1 53 | kind: ClusterRoleBinding 54 | metadata: 55 | name: exchangerates-operator 56 | subjects: 57 | - kind: ServiceAccount 58 | name: exchangerates-operator 59 | namespace: default 60 | apiGroup: "" 61 | roleRef: 62 | kind: ClusterRole 63 | name: exchangerates-operator 64 | apiGroup: rbac.authorization.k8s.io 65 | --------------------------------------------------------------------------------