diff --git a/docs/images/of-flagd-0.png b/docs/images/of-flagd-0.png index c51e033da..a910f0b4e 100644 Binary files a/docs/images/of-flagd-0.png and b/docs/images/of-flagd-0.png differ diff --git a/docs/images/of-flagd-1-source.excalidraw b/docs/images/of-flagd-1-source.excalidraw deleted file mode 100644 index b8a2cee9b..000000000 --- a/docs/images/of-flagd-1-source.excalidraw +++ /dev/null @@ -1,1629 +0,0 @@ -{ - "type": "excalidraw", - "version": 2, - "source": "https://excalidraw.com", - "elements": [ - { - "type": "rectangle", - "version": 737, - "versionNonce": 929680595, - "isDeleted": false, - "id": "Qj8NHgYS5nGleYfgiacky", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5365.937279878965, - "y": 1799.815465527041, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 1232.6989707341295, - "height": 270.8061135912699, - "seed": 313378793, - "groupIds": [], - "roundness": { - "type": 3 - }, - "boundElements": [], - "updated": 1674582882178, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 812, - "versionNonce": 980548859, - "isDeleted": false, - "id": "ARUohWRyaWHr62L4PbeJH", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5390.0190190503, - "y": 1823.1369138988373, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 183, - "height": 62, - "seed": 848836231, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270713, - "link": null, - "locked": false, - "fontSize": 49.17222744360915, - "fontFamily": 1, - "text": "Runtime", - "baseline": 44, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "Runtime" - }, - { - "type": "rectangle", - "version": 614, - "versionNonce": 954120819, - "isDeleted": false, - "id": "pQhuTP1tJtI-tddfIjOW0", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5254.300767142151, - "y": 2208.5850587810087, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 225.15620999743993, - "height": 108.80456349206288, - "seed": 1497948137, - "groupIds": [], - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "id": "l2FA7NanCDjDAtcS3THjp", - "type": "arrow" - }, - { - "id": "P_uDzl26hrV18cRIBX1ec", - "type": "arrow" - } - ], - "updated": 1674582882178, - "link": null, - "locked": false - }, - { - "type": "rectangle", - "version": 681, - "versionNonce": 1431738941, - "isDeleted": false, - "id": "uYYxxJHKeRFu0mvj7Af9S", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5550.636332618341, - "y": 2205.484860368311, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 225.15620999743993, - "height": 108.80456349206288, - "seed": 1648159369, - "groupIds": [], - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "id": "kq6n_nnQlXLYedWsmKUyw", - "type": "arrow" - }, - { - "id": "M-m92I9fFOsnHWllMynQK", - "type": "arrow" - } - ], - "updated": 1674582882178, - "link": null, - "locked": false - }, - { - "type": "rectangle", - "version": 749, - "versionNonce": 1151661075, - "isDeleted": false, - "id": "_EnzrMLnCCyDHDT7cD7Ul", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5838.260340554849, - "y": 2203.4759317968815, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 225.15620999743993, - "height": 108.80456349206288, - "seed": 1574289449, - "groupIds": [], - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "id": "CMr3bskke7SmWyw9SFDgp", - "type": "arrow" - }, - { - "id": "wHA_eH4fiSQ8inV2F0b2U", - "type": "arrow" - } - ], - "updated": 1674582882178, - "link": null, - "locked": false - }, - { - "type": "rectangle", - "version": 500, - "versionNonce": 1942793235, - "isDeleted": false, - "id": "ejwzeDlQYi3dgHZQ4vV1q", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5484.054947093877, - "y": 1965.4865866893217, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 367.4351895363416, - "height": 52.30034722222217, - "seed": 1171985001, - "groupIds": [ - "gU-17cboSlnnyNX_3kmx_" - ], - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "id": "fV9cxZ_nAVSfcOdX2Lh9o", - "type": "arrow" - }, - { - "id": "wHA_eH4fiSQ8inV2F0b2U", - "type": "arrow" - }, - { - "id": "M-m92I9fFOsnHWllMynQK", - "type": "arrow" - }, - { - "id": "P_uDzl26hrV18cRIBX1ec", - "type": "arrow" - }, - { - "id": "KYwr7D8nES1xtu6lzr-n4", - "type": "arrow" - } - ], - "updated": 1674582890735, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 550, - "versionNonce": 1534370869, - "isDeleted": false, - "id": "rPlVcV8WNTYL9tsg-7m3a", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5496.36928110599, - "y": 1976.9212704038166, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 356, - "height": 34, - "seed": 175928873, - "groupIds": [ - "gU-17cboSlnnyNX_3kmx_" - ], - "roundness": null, - "boundElements": [ - { - "id": "P_uDzl26hrV18cRIBX1ec", - "type": "arrow" - }, - { - "id": "M-m92I9fFOsnHWllMynQK", - "type": "arrow" - }, - { - "id": "wHA_eH4fiSQ8inV2F0b2U", - "type": "arrow" - }, - { - "id": "KYwr7D8nES1xtu6lzr-n4", - "type": "arrow" - } - ], - "updated": 1675110270713, - "link": null, - "locked": false, - "fontSize": 26.621499437095423, - "fontFamily": 1, - "text": "DataSync Channel listner ", - "baseline": 24, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "DataSync Channel listner " - }, - { - "type": "text", - "version": 162, - "versionNonce": 1052681627, - "isDeleted": false, - "id": "ul-opRWX9-q79QaF7heHL", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5332.914090394839, - "y": 2249.2782631460873, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 56, - "height": 35, - "seed": 419958727, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270713, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "K8s", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "K8s" - }, - { - "type": "text", - "version": 196, - "versionNonce": 236737941, - "isDeleted": false, - "id": "cZDICPD94kPhtc0_cuJ1b", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5637.339685632935, - "y": 2246.624245288945, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 47, - "height": 35, - "seed": 744467495, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270713, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "File", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "File" - }, - { - "type": "text", - "version": 244, - "versionNonce": 816659003, - "isDeleted": false, - "id": "whO7u38BygGnZV41usDUn", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5931.716421744044, - "y": 2241.1182929079937, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 25, - "height": 35, - "seed": 617082697, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270713, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "...", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "..." - }, - { - "type": "rectangle", - "version": 175, - "versionNonce": 829450483, - "isDeleted": false, - "id": "JecFfIYBRkJDq_-93C-ZW", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dotted", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5248.2094772996015, - "y": 2412.255693701643, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 230.22693452380918, - "height": 98.046875, - "seed": 1820105479, - "groupIds": [], - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "type": "text", - "id": "TXpC4rSrcDYYENP0SCQe8" - }, - { - "id": "l2FA7NanCDjDAtcS3THjp", - "type": "arrow" - } - ], - "updated": 1674582882178, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 140, - "versionNonce": 1736891125, - "isDeleted": false, - "id": "TXpC4rSrcDYYENP0SCQe8", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5317.322944561506, - "y": 2443.279131201643, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 92, - "height": 35, - "seed": 121928647, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270713, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Source", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "JecFfIYBRkJDq_-93C-ZW", - "originalText": "Source" - }, - { - "type": "arrow", - "version": 348, - "versionNonce": 1274860179, - "isDeleted": false, - "id": "l2FA7NanCDjDAtcS3THjp", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5359.798018966268, - "y": 2320.341011161961, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 2.0399305555556566, - "height": 88.82068452380963, - "seed": 1767992233, - "groupIds": [], - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1674582882179, - "link": null, - "locked": false, - "startBinding": { - "elementId": "pQhuTP1tJtI-tddfIjOW0", - "focus": 0.0737790241169211, - "gap": 2.9513888888895963 - }, - "endBinding": { - "elementId": "JecFfIYBRkJDq_-93C-ZW", - "focus": -0.0024778364546788807, - "gap": 3.09399801587233 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 2.0399305555556566, - 88.82068452380963 - ] - ] - }, - { - "type": "rectangle", - "version": 202, - "versionNonce": 484283421, - "isDeleted": false, - "id": "IAB_3SC3jJ-TBu6rm6lzj", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dotted", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5554.8904048789655, - "y": 2414.918764138152, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 230.22693452380918, - "height": 98.046875, - "seed": 1290443271, - "groupIds": [], - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "type": "text", - "id": "XzFX9X2kqOdSArRQDcJs-" - }, - { - "id": "kq6n_nnQlXLYedWsmKUyw", - "type": "arrow" - } - ], - "updated": 1674582882179, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 166, - "versionNonce": 1562393307, - "isDeleted": false, - "id": "XzFX9X2kqOdSArRQDcJs-", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5624.00387214087, - "y": 2445.942201638152, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 92, - "height": 35, - "seed": 1076440553, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Source", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "IAB_3SC3jJ-TBu6rm6lzj", - "originalText": "Source" - }, - { - "type": "arrow", - "version": 378, - "versionNonce": 263129213, - "isDeleted": false, - "id": "kq6n_nnQlXLYedWsmKUyw", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5662.912242718958, - "y": 2321.381479229977, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 2.0399305555556566, - "height": 88.82068452380963, - "seed": 822950409, - "groupIds": [], - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1674582882179, - "link": null, - "locked": false, - "startBinding": { - "elementId": "uYYxxJHKeRFu0mvj7Af9S", - "focus": 0.015062482842179266, - "gap": 7.092055369603258 - }, - "endBinding": { - "elementId": "IAB_3SC3jJ-TBu6rm6lzj", - "focus": -0.032841365170397284, - "gap": 4.7166003843653925 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 2.0399305555556566, - 88.82068452380963 - ] - ] - }, - { - "type": "rectangle", - "version": 252, - "versionNonce": 1291946451, - "isDeleted": false, - "id": "oGeYrfycfBoInZb4f533b", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dotted", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5843.264660831347, - "y": 2412.699022074659, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 230.22693452380918, - "height": 98.046875, - "seed": 684159175, - "groupIds": [], - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "type": "text", - "id": "cMsfn6ARds25qHXjfZwwx" - }, - { - "id": "CMr3bskke7SmWyw9SFDgp", - "type": "arrow" - } - ], - "updated": 1674582882179, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 214, - "versionNonce": 809088085, - "isDeleted": false, - "id": "cMsfn6ARds25qHXjfZwwx", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5912.378128093252, - "y": 2443.722459574659, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 92, - "height": 35, - "seed": 1043997481, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Source", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "oGeYrfycfBoInZb4f533b", - "originalText": "Source" - }, - { - "type": "arrow", - "version": 444, - "versionNonce": 511104883, - "isDeleted": false, - "id": "CMr3bskke7SmWyw9SFDgp", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5943.561875045572, - "y": 2313.4194685168463, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 2.0399305555556566, - "height": 88.82068452380963, - "seed": 766629609, - "groupIds": [], - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1674582882179, - "link": null, - "locked": false, - "startBinding": { - "elementId": "_EnzrMLnCCyDHDT7cD7Ul", - "focus": 0.0751327549660734, - "gap": 1.13897322790217 - }, - "endBinding": { - "elementId": "oGeYrfycfBoInZb4f533b", - "focus": -0.09816124044540017, - "gap": 10.458869034002873 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 2.0399305555556566, - 88.82068452380963 - ] - ] - }, - { - "type": "arrow", - "version": 882, - "versionNonce": 2082714035, - "isDeleted": false, - "id": "fV9cxZ_nAVSfcOdX2Lh9o", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5852.490136630218, - "y": 1990.3884698455684, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 312.08448203843, - "height": 0.31996508431871007, - "seed": 594202983, - "groupIds": [], - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1674582891427, - "link": null, - "locked": false, - "startBinding": { - "elementId": "ejwzeDlQYi3dgHZQ4vV1q", - "gap": 1, - "focus": -0.040204894641692246 - }, - "endBinding": { - "elementId": "RMgOg8Ct3di22nI94Ia4J", - "gap": 1.5526413690445224, - "focus": -0.018224554704818822 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 312.08448203843, - -0.31996508431871007 - ] - ] - }, - { - "type": "rectangle", - "version": 545, - "versionNonce": 1529966867, - "isDeleted": false, - "id": "RMgOg8Ct3di22nI94Ia4J", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 6166.127260037692, - "y": 1963.233930308787, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 395.10788690476244, - "height": 52.30034722222217, - "seed": 2016362311, - "groupIds": [ - "WN_YzaxCQFO0sT9vqXAcQ" - ], - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "id": "fV9cxZ_nAVSfcOdX2Lh9o", - "type": "arrow" - } - ], - "updated": 1674582882179, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 398, - "versionNonce": 1353328507, - "isDeleted": false, - "id": "1zaV80h1aQLmD3dAogyuP", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 6244.67363900595, - "y": 1974.7969263405328, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 223, - "height": 35, - "seed": 1139010185, - "groupIds": [ - "WN_YzaxCQFO0sT9vqXAcQ" - ], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "IEvaluator Impl", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "IEvaluator Impl" - }, - { - "type": "text", - "version": 225, - "versionNonce": 67789237, - "isDeleted": false, - "id": "gok76LXlUr-9a_jfiPBlZ", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5274.976762075911, - "y": 2125.632996920812, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 102, - "height": 35, - "seed": 598134473, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Update", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "Update" - }, - { - "type": "arrow", - "version": 1102, - "versionNonce": 1641515763, - "isDeleted": false, - "id": "P_uDzl26hrV18cRIBX1ec", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5357.308539257448, - "y": 2207.5850587810087, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 133.89604042228802, - "height": 189.4818119091674, - "seed": 221091367, - "groupIds": [], - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1674582887083, - "link": null, - "locked": false, - "startBinding": { - "elementId": "pQhuTP1tJtI-tddfIjOW0", - "focus": -0.32262917369065863, - "gap": 1 - }, - "endBinding": { - "elementId": "rPlVcV8WNTYL9tsg-7m3a", - "focus": 0.8748102301033904, - "gap": 9.557171462042334 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 133.89604042228802, - -189.4818119091674 - ] - ] - }, - { - "type": "arrow", - "version": 1185, - "versionNonce": 87730109, - "isDeleted": false, - "id": "M-m92I9fFOsnHWllMynQK", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5665.578963340132, - "y": 2202.1344368958366, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 2.559272165686707, - "height": 177.04477890577664, - "seed": 437765543, - "groupIds": [], - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1674582897378, - "link": null, - "locked": false, - "startBinding": { - "elementId": "uYYxxJHKeRFu0mvj7Af9S", - "focus": 0.028221973383330642, - "gap": 3.3504234724741764 - }, - "endBinding": { - "elementId": "rPlVcV8WNTYL9tsg-7m3a", - "focus": 0.0663096069898769, - "gap": 14.89151328987407 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -2.559272165686707, - -177.04477890577664 - ] - ] - }, - { - "type": "arrow", - "version": 1042, - "versionNonce": 1377669427, - "isDeleted": false, - "id": "wHA_eH4fiSQ8inV2F0b2U", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5930.809978015785, - "y": 2198.7767964315294, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 82.01547935708822, - "height": 180.76247985877876, - "seed": 1854310377, - "groupIds": [], - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1674582899191, - "link": null, - "locked": false, - "startBinding": { - "elementId": "_EnzrMLnCCyDHDT7cD7Ul", - "focus": 0.04944579683371673, - "gap": 4.699135365352049 - }, - "endBinding": { - "elementId": "rPlVcV8WNTYL9tsg-7m3a", - "focus": -0.8812303915353826, - "gap": 7.816171872564723 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -82.01547935708822, - -180.76247985877876 - ] - ] - }, - { - "type": "text", - "version": 241, - "versionNonce": 1636663323, - "isDeleted": false, - "id": "E4mML5dndYRQpcIfnHcv1", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5549.514225590072, - "y": 2126.889667923711, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 102, - "height": 35, - "seed": 214968807, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Update", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "Update" - }, - { - "type": "text", - "version": 281, - "versionNonce": 1746821909, - "isDeleted": false, - "id": "wzbQquX0RmBpx0oqUiKrE", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5786.792637218294, - "y": 2127.909014722018, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 102, - "height": 35, - "seed": 717951303, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Update", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "Update" - }, - { - "type": "text", - "version": 271, - "versionNonce": 2042414267, - "isDeleted": false, - "id": "gq-Bfr9dBscqonoaPZUZC", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5246.306896224069, - "y": 2343.532812954518, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 85, - "height": 35, - "seed": 1294784967, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Watch", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "Watch" - }, - { - "type": "text", - "version": 294, - "versionNonce": 552286325, - "isDeleted": false, - "id": "bulSeLRjl25qdCJ50oOPw", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5557.63053128154, - "y": 2344.1569149660113, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 85, - "height": 35, - "seed": 884393353, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Watch", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "Watch" - }, - { - "type": "text", - "version": 337, - "versionNonce": 551187803, - "isDeleted": false, - "id": "r3AdsmahZWmLMUqRHcv1p", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5861.135919212575, - "y": 2339.904939391299, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 33, - "height": 35, - "seed": 537917161, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "....", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "...." - }, - { - "type": "text", - "version": 277, - "versionNonce": 1211523541, - "isDeleted": false, - "id": "Su43cZCNrhnnL_B3ADHcN", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5957.539680222693, - "y": 1951.0461721603315, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 133, - "height": 35, - "seed": 75883273, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "SetState", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "SetState" - }, - { - "type": "rectangle", - "version": 611, - "versionNonce": 827082451, - "isDeleted": false, - "id": "GsTKcOyaEFWSwBNzjDIhr", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 6164.587548129213, - "y": 1824.9935352843254, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 395.10788690476244, - "height": 52.30034722222217, - "seed": 885089846, - "groupIds": [ - "fueUL-95g2Zv-xgD85IEp" - ], - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "id": "KYwr7D8nES1xtu6lzr-n4", - "type": "arrow" - }, - { - "id": "uJZ8gZOUWky50rKlnCr9P", - "type": "arrow" - } - ], - "updated": 1674582882179, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 463, - "versionNonce": 1178145275, - "isDeleted": false, - "id": "qGc6KKO0IQuqMTrvA6Et6", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 6264.6339270974695, - "y": 1836.556531316071, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 180, - "height": 35, - "seed": 2132262954, - "groupIds": [ - "fueUL-95g2Zv-xgD85IEp" - ], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "IService Impl", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "IService Impl" - }, - { - "type": "arrow", - "version": 971, - "versionNonce": 1631474781, - "isDeleted": false, - "id": "KYwr7D8nES1xtu6lzr-n4", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5846.942540667598, - "y": 1965.4491250844653, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 310.0279892991566, - "height": 113.99161363081066, - "seed": 271168234, - "groupIds": [], - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1674582907686, - "link": null, - "locked": false, - "startBinding": { - "elementId": "rPlVcV8WNTYL9tsg-7m3a", - "focus": 0.4309412897711203, - "gap": 11.472145319351398 - }, - "endBinding": { - "elementId": "GsTKcOyaEFWSwBNzjDIhr", - "focus": 0.7604613518133145, - "gap": 7.617018162458862 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 310.0279892991566, - -113.99161363081066 - ] - ] - }, - { - "type": "text", - "version": 336, - "versionNonce": 588603189, - "isDeleted": false, - "id": "ws-n8wRDKcmBAWf_estLO", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 5901.637585331596, - "y": 1874.456208895436, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 84, - "height": 35, - "seed": 1158122294, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Notify", - "baseline": 25, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "Notify" - }, - { - "type": "arrow", - "version": 843, - "versionNonce": 1873866259, - "isDeleted": false, - "id": "uJZ8gZOUWky50rKlnCr9P", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 6355.231537351993, - "y": 1818.410051538156, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 1.2627728174602453, - "height": 140.68698791963538, - "seed": 1149078582, - "groupIds": [], - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1674582882179, - "link": null, - "locked": false, - "startBinding": { - "elementId": "GsTKcOyaEFWSwBNzjDIhr", - "focus": -0.036421520227806294, - "gap": 6.58348374616935 - }, - "endBinding": { - "elementId": "sOx0wHAC0f5J7gJkaBL_Z", - "focus": 0.00533113751315642, - "gap": 4.494988702969067 - }, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - 1.2627728174602453, - -140.68698791963538 - ] - ] - }, - { - "type": "rectangle", - "version": 342, - "versionNonce": 1386934429, - "isDeleted": false, - "id": "sOx0wHAC0f5J7gJkaBL_Z", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dotted", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 6242.477243069691, - "y": 1575.1811999155516, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 230.22693452380918, - "height": 98.046875, - "seed": 1931527850, - "groupIds": [], - "roundness": { - "type": 3 - }, - "boundElements": [ - { - "type": "text", - "id": "9TGv3zWFdowwDKvP-7ozN" - }, - { - "id": "uJZ8gZOUWky50rKlnCr9P", - "type": "arrow" - } - ], - "updated": 1674582882179, - "link": null, - "locked": false - }, - { - "type": "text", - "version": 319, - "versionNonce": 1255873179, - "isDeleted": false, - "id": "9TGv3zWFdowwDKvP-7ozN", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 6288.590710331596, - "y": 1588.2046374155516, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 138, - "height": 70, - "seed": 1630785782, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 28, - "fontFamily": 1, - "text": "Event \nSubscriber", - "baseline": 60, - "textAlign": "center", - "verticalAlign": "middle", - "containerId": "sOx0wHAC0f5J7gJkaBL_Z", - "originalText": "Event Subscriber" - }, - { - "type": "text", - "version": 211, - "versionNonce": 938324117, - "isDeleted": false, - "id": "o7k2r3TlPyPgQ5J-74mQu", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "dotted", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 6194.888137730584, - "y": 2230.276922769087, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 202, - "height": 45, - "seed": 636663722, - "groupIds": [], - "roundness": null, - "boundElements": [], - "updated": 1675110270714, - "link": null, - "locked": false, - "fontSize": 36, - "fontFamily": 1, - "text": "ISync Impls", - "baseline": 32, - "textAlign": "center", - "verticalAlign": "top", - "containerId": null, - "originalText": "ISync Impls" - }, - { - "type": "arrow", - "version": 78, - "versionNonce": 2013578579, - "isDeleted": false, - "id": "vKvfrSvE_E4Eo7SCTH9_-", - "fillStyle": "hachure", - "strokeWidth": 1, - "strokeStyle": "solid", - "roughness": 1, - "opacity": 100, - "angle": 0, - "x": 6178.321849851795, - "y": 2255.5809000418144, - "strokeColor": "#000000", - "backgroundColor": "transparent", - "width": 69.4483901515141, - "height": 0, - "seed": 1689939178, - "groupIds": [], - "roundness": { - "type": 2 - }, - "boundElements": [], - "updated": 1674582882179, - "link": null, - "locked": false, - "startBinding": null, - "endBinding": null, - "lastCommittedPoint": null, - "startArrowhead": null, - "endArrowhead": "arrow", - "points": [ - [ - 0, - 0 - ], - [ - -69.4483901515141, - 0 - ] - ] - } - ], - "appState": { - "gridSize": null, - "viewBackgroundColor": "#ffffff" - }, - "files": {} -} \ No newline at end of file diff --git a/docs/images/of-flagd-1.png b/docs/images/of-flagd-1.png index 47ac5342c..5e059fcb1 100644 Binary files a/docs/images/of-flagd-1.png and b/docs/images/of-flagd-1.png differ diff --git a/docs/other_resources/high_level_architecture.md b/docs/other_resources/high_level_architecture.md index 6b8d39a02..15bbe4298 100644 --- a/docs/other_resources/high_level_architecture.md +++ b/docs/other_resources/high_level_architecture.md @@ -10,8 +10,7 @@ for flag configuration change notifications. The sync component has implementations to update flag configurations from various sources. The current implementation contain sync providers for files, K8s resources and HTTP endpoints. -The evaluator engine stores flag configurations updated from sync providers and perform the feature flag evaluations -based on evaluation requests coming from feature flag libraries. +The evaluation engine's role is twofold, it acts as an intermediary between configuration changes and the state store by interpreting change events and forwarding the necessary changes to the state store. It also performs the feature flag evaluations based on evaluation requests coming from feature flag libraries. The Runtime stays in between these components and coordinates operations. @@ -24,7 +23,7 @@ The Sync component contains implementations of the ISync interface. The interfac flag configurations watched by the respective implementation. For example, the file sync provider watches for a change (ex: - add, modify, remove) of a specific file in the file system. -The update provided by sync implementation is pushed to the evaluator engine and change notifications generated in the +The update provided by sync implementation is pushed to the evaluator engine, which interprets the event and forwards it to the state store. Change notifications generated in the process gets pushed to event subscribers. \ No newline at end of file diff --git a/go.sum b/go.sum index 92caae8e8..182dd0936 100644 --- a/go.sum +++ b/go.sum @@ -57,10 +57,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bufbuild/connect-go v1.4.1 h1:6usL3JGjKhxQpvDlizP7u8VfjAr1JkckcAUbrdcbgNY= -github.com/bufbuild/connect-go v1.4.1/go.mod h1:9iNvh/NOsfhNBUH5CtvXeVUskQO1xsrEviH7ZArwZ3I= -github.com/bufbuild/connect-go v1.5.0 h1:IfbgbzzaaZvF+OM3SfxO2EjtvNJarNAz2DIRuuNjAgc= -github.com/bufbuild/connect-go v1.5.0/go.mod h1:9iNvh/NOsfhNBUH5CtvXeVUskQO1xsrEviH7ZArwZ3I= github.com/bufbuild/connect-go v1.5.1 h1:ORhrSiu63hWxtuMmC/V1mKySSRhEySsW5RkHJcyJXBk= github.com/bufbuild/connect-go v1.5.1/go.mod h1:9iNvh/NOsfhNBUH5CtvXeVUskQO1xsrEviH7ZArwZ3I= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -285,14 +281,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.6.0 h1:9t9b9vRUbFq3C4qKFCGkVuq/fIHji802N1nrtkh1mNc= github.com/onsi/gomega v1.24.1 h1:KORJXNNTzJXzu4ScJWssJfJMnJ+2QJqhoQSRwNlze9E= -github.com/open-feature/open-feature-operator v0.2.24 h1:6UwfHO7pa2WDDpdyL+hzYwukbooAA2IZFgyj5xga2vw= -github.com/open-feature/open-feature-operator v0.2.24/go.mod h1:6zsu3m2sa8b4qJlHIAp1Kuc80mCAOAkBCkvDTTyv9ZY= -github.com/open-feature/open-feature-operator v0.2.25 h1:6X1dn7YTTCxRj7Sq6NR3ThDvXYt+4VPPC1GP7D5GD+Q= -github.com/open-feature/open-feature-operator v0.2.25/go.mod h1:8OFtVXXdVpZTSx1vHravbTYup4iyeb+PLmiKbRL11TA= -github.com/open-feature/open-feature-operator v0.2.26 h1:nv3Bln6Zvkc0fXz1/XpQR5TtiXn8KZ/9r85y/jWGNE0= -github.com/open-feature/open-feature-operator v0.2.26/go.mod h1:bQncVK7hvhj5QStPwexxQ1aArPwox2Y1vWrVei/qIFg= -github.com/open-feature/open-feature-operator v0.2.27 h1:OIPEVrEOK39mLeImKrcLnd1AVClj7VrEMOtnZjHLXxY= -github.com/open-feature/open-feature-operator v0.2.27/go.mod h1:bQncVK7hvhj5QStPwexxQ1aArPwox2Y1vWrVei/qIFg= github.com/open-feature/open-feature-operator v0.2.28 h1:qzzVq8v9G7aXO7luocO/wQCGnTJjtcQh75mDOqjnFxo= github.com/open-feature/open-feature-operator v0.2.28/go.mod h1:bQncVK7hvhj5QStPwexxQ1aArPwox2Y1vWrVei/qIFg= github.com/open-feature/schemas v0.2.8 h1:oA75hJXpOd9SFgmNI2IAxWZkwzQPUDm7Jyyh3q489wM= @@ -737,10 +725,6 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.52.0 h1:kd48UiU7EHsV4rnLyOJRuP/Il/UHE7gdDAQ+SZI7nZk= -google.golang.org/grpc v1.52.0/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= -google.golang.org/grpc v1.52.1 h1:2NpOPk5g5Xtb0qebIEs7hNIa++PdtZLo2AQUpc1YnSU= -google.golang.org/grpc v1.52.1/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.52.3 h1:pf7sOysg4LdgBqduXveGKrcEwbStiK2rtfghdzlUYDQ= google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5vorUY= google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= @@ -793,16 +777,12 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ= k8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg= -k8s.io/apiextensions-apiserver v0.26.0 h1:Gy93Xo1eg2ZIkNX/8vy5xviVSxwQulsnUdQ00nEdpDo= -k8s.io/apiextensions-apiserver v0.26.0/go.mod h1:7ez0LTiyW5nq3vADtK6C3kMESxadD51Bh6uz3JOlqWQ= k8s.io/apiextensions-apiserver v0.26.1 h1:cB8h1SRk6e/+i3NOrQgSFij1B2S0Y0wDoNl66bn8RMI= k8s.io/apiextensions-apiserver v0.26.1/go.mod h1:AptjOSXDGuE0JICx/Em15PaoO7buLwTs0dGleIHixSM= k8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ= k8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= k8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU= k8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE= -k8s.io/component-base v0.26.0 h1:0IkChOCohtDHttmKuz+EP3j3+qKmV55rM9gIFTXA7Vs= -k8s.io/component-base v0.26.0/go.mod h1:lqHwlfV1/haa14F/Z5Zizk5QmzaVf23nQzCwVOQpfC8= k8s.io/component-base v0.26.1 h1:4ahudpeQXHZL5kko+iDHqLj/FSGAEUnSVO0EBbgDd+4= k8s.io/component-base v0.26.1/go.mod h1:VHrLR0b58oC035w6YQiBSbtsf0ThuSwXP+p5dD/kAWU= k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= @@ -814,12 +794,6 @@ k8s.io/utils v0.0.0-20221128185143-99ec85e7a448/go.mod h1:OLgZIPagt7ERELqWJFomSt rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.14.1 h1:vThDes9pzg0Y+UbCPY3Wj34CGIYPgdmspPm2GIpxpzM= -sigs.k8s.io/controller-runtime v0.14.1/go.mod h1:GaRkrY8a7UZF0kqFFbUKG7n9ICiTY5T55P1RiE3UZlU= -sigs.k8s.io/controller-runtime v0.14.2 h1:P6IwDhbsRWsBClt/8/h8Zy36bCuGuW5Op7MHpFrN/60= -sigs.k8s.io/controller-runtime v0.14.2/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= -sigs.k8s.io/controller-runtime v0.14.3 h1:F1JutCoGfSDRiayjAaWcB8SC4BwIt6qkZ/TwiVY8ZRI= -sigs.k8s.io/controller-runtime v0.14.3/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= sigs.k8s.io/controller-runtime v0.14.4 h1:Kd/Qgx5pd2XUL08eOV2vwIq3L9GhIbJ5Nxengbd4/0M= sigs.k8s.io/controller-runtime v0.14.4/go.mod h1:WqIdsAY6JBsjfc/CqO0CORmNtoCtE4S6qbPc9s68h+0= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= diff --git a/pkg/eval/fractional_evaluation_test.go b/pkg/eval/fractional_evaluation_test.go index 6c78fc1a3..3c23615a5 100644 --- a/pkg/eval/fractional_evaluation_test.go +++ b/pkg/eval/fractional_evaluation_test.go @@ -1,9 +1,10 @@ package eval import ( - "sync" "testing" + "github.com/open-feature/flagd/pkg/store" + "github.com/open-feature/flagd/pkg/logger" "github.com/open-feature/flagd/pkg/model" "google.golang.org/protobuf/types/known/structpb" @@ -11,8 +12,7 @@ import ( func TestFractionalEvaluation(t *testing.T) { flags := Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + Flags: map[string]model.Flag{ "headerColor": { State: "ENABLED", DefaultVariant: "red", @@ -115,8 +115,7 @@ func TestFractionalEvaluation(t *testing.T) { }, "non even split": { flags: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + Flags: map[string]model.Flag{ "headerColor": { State: "ENABLED", DefaultVariant: "red", @@ -167,8 +166,7 @@ func TestFractionalEvaluation(t *testing.T) { }, "fallback to default variant if no email provided": { flags: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + Flags: map[string]model.Flag{ "headerColor": { State: "ENABLED", DefaultVariant: "red", @@ -210,8 +208,7 @@ func TestFractionalEvaluation(t *testing.T) { }, "fallback to default variant if invalid variant as result of fractional evaluation": { flags: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + Flags: map[string]model.Flag{ "headerColor": { State: "ENABLED", DefaultVariant: "red", @@ -245,8 +242,7 @@ func TestFractionalEvaluation(t *testing.T) { }, "fallback to default variant if percentages don't sum to 100": { flags: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + Flags: map[string]model.Flag{ "headerColor": { State: "ENABLED", DefaultVariant: "red", @@ -287,10 +283,10 @@ func TestFractionalEvaluation(t *testing.T) { for name, tt := range tests { t.Run(name, func(t *testing.T) { je := NewJSONEvaluator(logger.NewLogger(nil, false)) - je.state = tt.flags + je.store.Flags = tt.flags.Flags value, variant, reason, err := resolve[string]( - reqID, tt.flagKey, tt.context, je.evaluateVariant, je.state.Flags[tt.flagKey].Variants, + reqID, tt.flagKey, tt.context, je.evaluateVariant, je.store.Flags[tt.flagKey].Variants, ) if value != tt.expectedValue { @@ -314,7 +310,7 @@ func TestFractionalEvaluation(t *testing.T) { func BenchmarkFractionalEvaluation(b *testing.B) { flags := Flags{ - Flags: map[string]Flag{ + Flags: map[string]model.Flag{ "headerColor": { State: "ENABLED", DefaultVariant: "red", @@ -419,10 +415,10 @@ func BenchmarkFractionalEvaluation(b *testing.B) { reqID := "test" for name, tt := range tests { b.Run(name, func(b *testing.B) { - je := JSONEvaluator{state: tt.flags} + je := JSONEvaluator{store: &store.Flags{Flags: tt.flags.Flags}} for i := 0; i < b.N; i++ { value, variant, reason, err := resolve[string]( - reqID, tt.flagKey, tt.context, je.evaluateVariant, je.state.Flags[tt.flagKey].Variants, + reqID, tt.flagKey, tt.context, je.evaluateVariant, je.store.Flags[tt.flagKey].Variants, ) if value != tt.expectedValue { diff --git a/pkg/eval/ievaluator.go b/pkg/eval/ievaluator.go index 071d9fadc..c1df1f146 100644 --- a/pkg/eval/ievaluator.go +++ b/pkg/eval/ievaluator.go @@ -5,20 +5,6 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) -type StateChangeNotificationType string - -const ( - NotificationDelete StateChangeNotificationType = "delete" - NotificationCreate StateChangeNotificationType = "write" - NotificationUpdate StateChangeNotificationType = "update" -) - -type StateChangeNotification struct { - Type StateChangeNotificationType `json:"type"` - Source string `json:"source"` - FlagKey string `json:"flagKey"` -} - type AnyValue struct { Value interface{} Variant string diff --git a/pkg/eval/json_evaluator.go b/pkg/eval/json_evaluator.go index 15196c05b..b8668ae54 100644 --- a/pkg/eval/json_evaluator.go +++ b/pkg/eval/json_evaluator.go @@ -8,8 +8,8 @@ import ( "regexp" "strconv" "strings" - mxSync "sync" + "github.com/open-feature/flagd/pkg/store" "github.com/open-feature/flagd/pkg/sync" "github.com/diegoholiveira/jsonlogic/v3" @@ -28,7 +28,7 @@ func init() { } type JSONEvaluator struct { - state Flags + store *store.Flags Logger *logger.Logger } @@ -46,21 +46,14 @@ func NewJSONEvaluator(logger *logger.Logger) *JSONEvaluator { zap.String("component", "evaluator"), zap.String("evaluator", "json"), ), - state: Flags{ - Flags: map[string]Flag{}, - mx: &mxSync.RWMutex{}, - }, + store: store.NewFlags(), } jsonlogic.AddOperator("fractionalEvaluation", ev.fractionalEvaluation) return &ev } func (je *JSONEvaluator) GetState() (string, error) { - data, err := json.Marshal(&je.state) - if err != nil { - return "", err - } - return string(data), nil + return je.store.String() } func (je *JSONEvaluator) SetState(payload sync.DataSync) (map[string]interface{}, error) { @@ -72,13 +65,13 @@ func (je *JSONEvaluator) SetState(payload sync.DataSync) (map[string]interface{} switch payload.Type { case sync.ALL: - return je.state.Merge(je.Logger, payload.Source, newFlags), nil + return je.store.Merge(je.Logger, payload.Source, newFlags.Flags), nil case sync.ADD: - return je.state.Add(je.Logger, payload.Source, newFlags), nil + return je.store.Add(je.Logger, payload.Source, newFlags.Flags), nil case sync.UPDATE: - return je.state.Update(je.Logger, payload.Source, newFlags), nil + return je.store.Update(je.Logger, payload.Source, newFlags.Flags), nil case sync.DELETE: - return je.state.Delete(je.Logger, payload.Source, newFlags), nil + return je.store.DeleteFlags(je.Logger, payload.Source, newFlags.Flags), nil default: return nil, fmt.Errorf("unsupported sync type: %d", payload.Type) } @@ -112,9 +105,8 @@ func (je *JSONEvaluator) ResolveAllValues(reqID string, context *structpb.Struct var variant string var reason string var err error - je.state.mx.RLock() - defer je.state.mx.RUnlock() - for flagKey, flag := range je.state.Flags { + allFlags := je.store.GetAll() + for flagKey, flag := range allFlags { defaultValue := flag.Variants[flag.DefaultVariant] switch defaultValue.(type) { case bool: @@ -123,7 +115,7 @@ func (je *JSONEvaluator) ResolveAllValues(reqID string, context *structpb.Struct flagKey, context, je.evaluateVariant, - je.state.Flags[flagKey].Variants, + allFlags[flagKey].Variants, ) case string: value, variant, reason, err = resolve[string]( @@ -131,7 +123,7 @@ func (je *JSONEvaluator) ResolveAllValues(reqID string, context *structpb.Struct flagKey, context, je.evaluateVariant, - je.state.Flags[flagKey].Variants, + allFlags[flagKey].Variants, ) case float64: value, variant, reason, err = resolve[float64]( @@ -139,7 +131,7 @@ func (je *JSONEvaluator) ResolveAllValues(reqID string, context *structpb.Struct flagKey, context, je.evaluateVariant, - je.state.Flags[flagKey].Variants, + allFlags[flagKey].Variants, ) case map[string]any: value, variant, reason, err = resolve[map[string]any]( @@ -147,7 +139,7 @@ func (je *JSONEvaluator) ResolveAllValues(reqID string, context *structpb.Struct flagKey, context, je.evaluateVariant, - je.state.Flags[flagKey].Variants, + allFlags[flagKey].Variants, ) } if err != nil { @@ -165,10 +157,9 @@ func (je *JSONEvaluator) ResolveBooleanValue(reqID string, flagKey string, conte reason string, err error, ) { - je.state.mx.RLock() - defer je.state.mx.RUnlock() je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating boolean flag: %s", flagKey)) - return resolve[bool](reqID, flagKey, context, je.evaluateVariant, je.state.Flags[flagKey].Variants) + flag, _ := je.store.Get(flagKey) + return resolve[bool](reqID, flagKey, context, je.evaluateVariant, flag.Variants) } func (je *JSONEvaluator) ResolveStringValue(reqID string, flagKey string, context *structpb.Struct) ( @@ -177,10 +168,9 @@ func (je *JSONEvaluator) ResolveStringValue(reqID string, flagKey string, contex reason string, err error, ) { - je.state.mx.RLock() - defer je.state.mx.RUnlock() je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating string flag: %s", flagKey)) - return resolve[string](reqID, flagKey, context, je.evaluateVariant, je.state.Flags[flagKey].Variants) + flag, _ := je.store.Get(flagKey) + return resolve[string](reqID, flagKey, context, je.evaluateVariant, flag.Variants) } func (je *JSONEvaluator) ResolveFloatValue(reqID string, flagKey string, context *structpb.Struct) ( @@ -189,11 +179,10 @@ func (je *JSONEvaluator) ResolveFloatValue(reqID string, flagKey string, context reason string, err error, ) { - je.state.mx.RLock() - defer je.state.mx.RUnlock() je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating float flag: %s", flagKey)) + flag, _ := je.store.Get(flagKey) value, variant, reason, err = resolve[float64]( - reqID, flagKey, context, je.evaluateVariant, je.state.Flags[flagKey].Variants) + reqID, flagKey, context, je.evaluateVariant, flag.Variants) return } @@ -203,12 +192,11 @@ func (je *JSONEvaluator) ResolveIntValue(reqID string, flagKey string, context * reason string, err error, ) { - je.state.mx.RLock() - defer je.state.mx.RUnlock() je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating int flag: %s", flagKey)) + flag, _ := je.store.Get(flagKey) var val float64 val, variant, reason, err = resolve[float64]( - reqID, flagKey, context, je.evaluateVariant, je.state.Flags[flagKey].Variants) + reqID, flagKey, context, je.evaluateVariant, flag.Variants) value = int64(val) return } @@ -219,10 +207,9 @@ func (je *JSONEvaluator) ResolveObjectValue(reqID string, flagKey string, contex reason string, err error, ) { - je.state.mx.RLock() - defer je.state.mx.RUnlock() je.Logger.DebugWithID(reqID, fmt.Sprintf("evaluating object flag: %s", flagKey)) - return resolve[map[string]any](reqID, flagKey, context, je.evaluateVariant, je.state.Flags[flagKey].Variants) + flag, _ := je.store.Get(flagKey) + return resolve[map[string]any](reqID, flagKey, context, je.evaluateVariant, flag.Variants) } // runs the rules (if defined) to determine the variant, otherwise falling through to the default @@ -231,7 +218,7 @@ func (je *JSONEvaluator) evaluateVariant( flagKey string, context *structpb.Struct, ) (variant string, reason string, err error) { - flag, ok := je.state.Flags[flagKey] + flag, ok := je.store.Get(flagKey) if !ok { // flag not found je.Logger.DebugWithID(reqID, fmt.Sprintf("requested flag could not be found: %s", flagKey)) diff --git a/pkg/eval/json_evaluator_model.go b/pkg/eval/json_evaluator_model.go index 8dd8e157f..c51efe61a 100644 --- a/pkg/eval/json_evaluator_model.go +++ b/pkg/eval/json_evaluator_model.go @@ -2,182 +2,14 @@ package eval import ( "encoding/json" - "fmt" - "reflect" - "sync" - "github.com/open-feature/flagd/pkg/logger" + "github.com/open-feature/flagd/pkg/model" ) -type Flag struct { - State string `json:"state"` - DefaultVariant string `json:"defaultVariant"` - Variants map[string]any `json:"variants"` - Targeting json.RawMessage `json:"targeting,omitempty"` - Source string `json:"source"` -} - type Evaluators struct { Evaluators map[string]json.RawMessage `json:"$evaluators"` } type Flags struct { - mx *sync.RWMutex - Flags map[string]Flag `json:"flags"` -} - -// Add new flags from source. The implementation is not thread safe -func (f Flags) Add(logger *logger.Logger, source string, ff Flags) map[string]interface{} { - notifications := map[string]interface{}{} - - for k, newFlag := range ff.Flags { - f.mx.RLock() - storedFlag, ok := f.Flags[k] - f.mx.RUnlock() - if ok && storedFlag.Source != source { - logger.Warn(fmt.Sprintf( - "flag with key %s from source %s already exist, overriding this with flag from source %s", - k, - storedFlag.Source, - source, - )) - } - - notifications[k] = map[string]interface{}{ - "type": string(NotificationCreate), - "source": source, - } - - // Store the new version of the flag - newFlag.Source = source - f.mx.Lock() - f.Flags[k] = newFlag - f.mx.Unlock() - } - - return notifications -} - -// Update existing flags from source. The implementation is not thread safe -func (f Flags) Update(logger *logger.Logger, source string, ff Flags) map[string]interface{} { - notifications := map[string]interface{}{} - - for k, flag := range ff.Flags { - f.mx.RLock() - storedFlag, ok := f.Flags[k] - f.mx.RUnlock() - if !ok { - logger.Warn( - fmt.Sprintf("failed to update the flag, flag with key %s from source %s does not exist.", - k, - source)) - - continue - } - if storedFlag.Source != source { - logger.Warn(fmt.Sprintf( - "flag with key %s from source %s already exist, overriding this with flag from source %s", - k, - storedFlag.Source, - source, - )) - } - - notifications[k] = map[string]interface{}{ - "type": string(NotificationUpdate), - "source": source, - } - - flag.Source = source - f.mx.Lock() - f.Flags[k] = flag - f.mx.Unlock() - } - - return notifications -} - -// Delete matching flags from source. The implementation is not thread safe -func (f Flags) Delete(logger *logger.Logger, source string, ff Flags) map[string]interface{} { - notifications := map[string]interface{}{} - - for k := range ff.Flags { - f.mx.RLock() - _, ok := f.Flags[k] - f.mx.RUnlock() - if ok { - notifications[k] = map[string]interface{}{ - "type": string(NotificationDelete), - "source": source, - } - - f.mx.Lock() - delete(f.Flags, k) - f.mx.Unlock() - } else { - logger.Warn( - fmt.Sprintf("failed to remove flag, flag with key %s from source %s does not exisit.", - k, - source)) - } - } - - return notifications -} - -// Merge provided flags from source with currently stored flags. The implementation is not thread safe -func (f Flags) Merge(logger *logger.Logger, source string, ff Flags) map[string]interface{} { - notifications := map[string]interface{}{} - - f.mx.Lock() - for k, v := range f.Flags { - if v.Source == source { - if _, ok := ff.Flags[k]; !ok { - // flag has been deleted - delete(f.Flags, k) - notifications[k] = map[string]interface{}{ - "type": string(NotificationDelete), - "source": source, - } - continue - } - } - } - f.mx.Unlock() - - for k, newFlag := range ff.Flags { - newFlag.Source = source - - f.mx.RLock() - storedFlag, ok := f.Flags[k] - f.mx.RUnlock() - if !ok { - notifications[k] = map[string]interface{}{ - "type": string(NotificationCreate), - "source": source, - } - } else if !reflect.DeepEqual(storedFlag, newFlag) { - if storedFlag.Source != source { - logger.Warn( - fmt.Sprintf( - "key value: %s is duplicated across multiple sources this can lead to unexpected behavior: %s, %s", - k, - storedFlag.Source, - source, - ), - ) - } - notifications[k] = map[string]interface{}{ - "type": string(NotificationUpdate), - "source": source, - } - } - - f.mx.Lock() - // Store the new version of the flag - f.Flags[k] = newFlag - f.mx.Unlock() - } - - return notifications + Flags map[string]model.Flag `json:"flags"` } diff --git a/pkg/model/flag.go b/pkg/model/flag.go new file mode 100644 index 000000000..c83066518 --- /dev/null +++ b/pkg/model/flag.go @@ -0,0 +1,15 @@ +package model + +import "encoding/json" + +type Flag struct { + State string `json:"state"` + DefaultVariant string `json:"defaultVariant"` + Variants map[string]any `json:"variants"` + Targeting json.RawMessage `json:"targeting,omitempty"` + Source string `json:"source"` +} + +type Evaluators struct { + Evaluators map[string]json.RawMessage `json:"$evaluators"` +} diff --git a/pkg/model/notification.go b/pkg/model/notification.go new file mode 100644 index 000000000..1c3c3db00 --- /dev/null +++ b/pkg/model/notification.go @@ -0,0 +1,15 @@ +package model + +type StateChangeNotificationType string + +const ( + NotificationDelete StateChangeNotificationType = "delete" + NotificationCreate StateChangeNotificationType = "write" + NotificationUpdate StateChangeNotificationType = "update" +) + +type StateChangeNotification struct { + Type StateChangeNotificationType `json:"type"` + Source string `json:"source"` + FlagKey string `json:"flagKey"` +} diff --git a/pkg/store/flags.go b/pkg/store/flags.go new file mode 100644 index 000000000..3627c6f36 --- /dev/null +++ b/pkg/store/flags.go @@ -0,0 +1,205 @@ +package store + +import ( + "encoding/json" + "fmt" + "reflect" + "sync" + + "github.com/open-feature/flagd/pkg/logger" + + "github.com/open-feature/flagd/pkg/model" +) + +type Flags struct { + mx sync.RWMutex + Flags map[string]model.Flag `json:"flags"` +} + +func NewFlags() *Flags { + return &Flags{Flags: map[string]model.Flag{}} +} + +func (f *Flags) Set(key string, flag model.Flag) { + f.mx.Lock() + defer f.mx.Unlock() + f.Flags[key] = flag +} + +func (f *Flags) Get(key string) (model.Flag, bool) { + f.mx.RLock() + defer f.mx.RUnlock() + flag, ok := f.Flags[key] + + return flag, ok +} + +func (f *Flags) Delete(key string) { + f.mx.Lock() + defer f.mx.Unlock() + delete(f.Flags, key) +} + +func (f *Flags) String() (string, error) { + f.mx.RLock() + defer f.mx.RUnlock() + bytes, err := json.Marshal(f) + if err != nil { + return "", err + } + + return string(bytes), nil +} + +// GetAll returns a copy of the store's state (copy in order to be concurrency safe) +func (f *Flags) GetAll() map[string]model.Flag { + f.mx.RLock() + defer f.mx.RUnlock() + state := make(map[string]model.Flag, len(f.Flags)) + + for key, flag := range f.Flags { + state[key] = flag + } + + return state +} + +// Add new flags from source. +func (f *Flags) Add(logger *logger.Logger, source string, flags map[string]model.Flag) map[string]interface{} { + notifications := map[string]interface{}{} + + for k, newFlag := range flags { + storedFlag, ok := f.Get(k) + if ok && storedFlag.Source != source { + logger.Warn(fmt.Sprintf( + "flag with key %s from source %s already exist, overriding this with flag from source %s", + k, + storedFlag.Source, + source, + )) + } + + notifications[k] = map[string]interface{}{ + "type": string(model.NotificationCreate), + "source": source, + } + + // Store the new version of the flag + newFlag.Source = source + f.Set(k, newFlag) + } + + return notifications +} + +// Update existing flags from source. +func (f *Flags) Update(logger *logger.Logger, source string, flags map[string]model.Flag) map[string]interface{} { + notifications := map[string]interface{}{} + + for k, flag := range flags { + storedFlag, ok := f.Get(k) + if !ok { + logger.Warn( + fmt.Sprintf("failed to update the flag, flag with key %s from source %s does not exist.", + k, + source)) + + continue + } + if storedFlag.Source != source { + logger.Warn(fmt.Sprintf( + "flag with key %s from source %s already exist, overriding this with flag from source %s", + k, + storedFlag.Source, + source, + )) + } + + notifications[k] = map[string]interface{}{ + "type": string(model.NotificationUpdate), + "source": source, + } + + flag.Source = source + f.Set(k, flag) + } + + return notifications +} + +// DeleteFlags matching flags from source. +func (f *Flags) DeleteFlags(logger *logger.Logger, source string, flags map[string]model.Flag) map[string]interface{} { + notifications := map[string]interface{}{} + + for k := range flags { + _, ok := f.Get(k) + if ok { + notifications[k] = map[string]interface{}{ + "type": string(model.NotificationDelete), + "source": source, + } + + f.Delete(k) + } else { + logger.Warn( + fmt.Sprintf("failed to remove flag, flag with key %s from source %s does not exisit.", + k, + source)) + } + } + + return notifications +} + +// Merge provided flags from source with currently stored flags. +func (f *Flags) Merge(logger *logger.Logger, source string, flags map[string]model.Flag) map[string]interface{} { + notifications := map[string]interface{}{} + + f.mx.Lock() + for k, v := range f.Flags { + if v.Source == source { + if _, ok := flags[k]; !ok { + // flag has been deleted + delete(f.Flags, k) + notifications[k] = map[string]interface{}{ + "type": string(model.NotificationDelete), + "source": source, + } + continue + } + } + } + f.mx.Unlock() + + for k, newFlag := range flags { + newFlag.Source = source + + storedFlag, ok := f.Get(k) + if !ok { + notifications[k] = map[string]interface{}{ + "type": string(model.NotificationCreate), + "source": source, + } + } else if !reflect.DeepEqual(storedFlag, newFlag) { + if storedFlag.Source != source { + logger.Warn( + fmt.Sprintf( + "key value: %s is duplicated across multiple sources this can lead to unexpected behavior: %s, %s", + k, + storedFlag.Source, + source, + ), + ) + } + notifications[k] = map[string]interface{}{ + "type": string(model.NotificationUpdate), + "source": source, + } + } + + // Store the new version of the flag + f.Set(k, newFlag) + } + + return notifications +} diff --git a/pkg/eval/json_evaluator_model_test.go b/pkg/store/flags_test.go similarity index 61% rename from pkg/eval/json_evaluator_model_test.go rename to pkg/store/flags_test.go index dd2582c0d..a3c0741c9 100644 --- a/pkg/eval/json_evaluator_model_test.go +++ b/pkg/store/flags_test.go @@ -1,9 +1,10 @@ -package eval +package store import ( - "sync" "testing" + "github.com/open-feature/flagd/pkg/model" + "github.com/open-feature/flagd/pkg/logger" "github.com/stretchr/testify/require" ) @@ -12,70 +13,65 @@ func TestMergeFlags(t *testing.T) { t.Parallel() tests := []struct { name string - current Flags - new Flags + current *Flags + new map[string]model.Flag newSource string - want Flags + want *Flags wantNotifs map[string]interface{} }{ { name: "both nil", - current: Flags{ - mx: &sync.RWMutex{}, + current: &Flags{ Flags: nil, }, - new: Flags{Flags: nil}, - want: Flags{Flags: map[string]Flag{}}, + new: nil, + want: &Flags{Flags: map[string]model.Flag{}}, wantNotifs: map[string]interface{}{}, }, { name: "both empty flags", - current: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{}, + current: &Flags{ + Flags: map[string]model.Flag{}, }, - new: Flags{Flags: map[string]Flag{}}, - want: Flags{Flags: map[string]Flag{}}, + new: map[string]model.Flag{}, + want: &Flags{Flags: map[string]model.Flag{}}, wantNotifs: map[string]interface{}{}, }, { name: "empty current", - current: Flags{ - mx: &sync.RWMutex{}, + current: &Flags{ Flags: nil, }, - new: Flags{Flags: map[string]Flag{}}, - want: Flags{Flags: map[string]Flag{}}, + new: map[string]model.Flag{}, + want: &Flags{Flags: map[string]model.Flag{}}, wantNotifs: map[string]interface{}{}, }, { name: "empty new", - current: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{}, + current: &Flags{ + Flags: map[string]model.Flag{}, }, - new: Flags{Flags: nil}, - want: Flags{Flags: map[string]Flag{}}, + new: nil, + want: &Flags{Flags: map[string]model.Flag{}}, wantNotifs: map[string]interface{}{}, }, { name: "extra fields on each", - current: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + current: &Flags{ + Flags: map[string]model.Flag{ "waka": { DefaultVariant: "off", Source: "1", }, }, }, - new: Flags{Flags: map[string]Flag{ + new: map[string]model.Flag{ "paka": { DefaultVariant: "on", }, - }}, + }, newSource: "2", - want: Flags{Flags: map[string]Flag{ + want: &Flags{Flags: map[string]model.Flag{ "waka": { DefaultVariant: "off", Source: "1", @@ -91,15 +87,14 @@ func TestMergeFlags(t *testing.T) { }, { name: "override", - current: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{"waka": {DefaultVariant: "off"}}, + current: &Flags{ + Flags: map[string]model.Flag{"waka": {DefaultVariant: "off"}}, }, - new: Flags{Flags: map[string]Flag{ + new: map[string]model.Flag{ "waka": {DefaultVariant: "on"}, "paka": {DefaultVariant: "on"}, - }}, - want: Flags{Flags: map[string]Flag{ + }, + want: &Flags{Flags: map[string]model.Flag{ "waka": {DefaultVariant: "on"}, "paka": {DefaultVariant: "on"}, }}, @@ -110,14 +105,13 @@ func TestMergeFlags(t *testing.T) { }, { name: "identical", - current: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{"hello": {DefaultVariant: "off"}}, + current: &Flags{ + Flags: map[string]model.Flag{"hello": {DefaultVariant: "off"}}, }, - new: Flags{Flags: map[string]Flag{ + new: map[string]model.Flag{ "hello": {DefaultVariant: "off"}, - }}, - want: Flags{Flags: map[string]Flag{ + }, + want: &Flags{Flags: map[string]model.Flag{ "hello": {DefaultVariant: "off"}, }}, wantNotifs: map[string]interface{}{}, @@ -142,35 +136,31 @@ func TestFlags_Add(t *testing.T) { type request struct { source string - flags Flags + flags map[string]model.Flag } tests := []struct { name string - storedState Flags + storedState *Flags addRequest request - expectedState Flags + expectedState *Flags expectedNotificationKeys []string }{ { name: "Add success", - storedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + storedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource}, }, }, addRequest: request{ source: mockSource, - flags: Flags{ - Flags: map[string]Flag{ - "B": {Source: mockSource}, - }, + flags: map[string]model.Flag{ + "B": {Source: mockSource}, }, }, - expectedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + expectedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource}, "B": {Source: mockSource}, }, @@ -179,24 +169,20 @@ func TestFlags_Add(t *testing.T) { }, { name: "Add multiple success", - storedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + storedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource}, }, }, addRequest: request{ source: mockSource, - flags: Flags{ - Flags: map[string]Flag{ - "B": {Source: mockSource}, - "C": {Source: mockSource}, - }, + flags: map[string]model.Flag{ + "B": {Source: mockSource}, + "C": {Source: mockSource}, }, }, - expectedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + expectedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource}, "B": {Source: mockSource}, "C": {Source: mockSource}, @@ -206,23 +192,19 @@ func TestFlags_Add(t *testing.T) { }, { name: "Add success - conflict and override", - storedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + storedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource}, }, }, addRequest: request{ source: mockOverrideSource, - flags: Flags{ - Flags: map[string]Flag{ - "A": {Source: mockOverrideSource}, - }, + flags: map[string]model.Flag{ + "A": {Source: mockOverrideSource}, }, }, - expectedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + expectedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockOverrideSource}, }, }, @@ -250,35 +232,31 @@ func TestFlags_Update(t *testing.T) { type request struct { source string - flags Flags + flags map[string]model.Flag } tests := []struct { name string - storedState Flags + storedState *Flags UpdateRequest request - expectedState Flags + expectedState *Flags expectedNotificationKeys []string }{ { name: "Update success", - storedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + storedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource, DefaultVariant: "True"}, }, }, UpdateRequest: request{ source: mockSource, - flags: Flags{ - Flags: map[string]Flag{ - "A": {Source: mockSource, DefaultVariant: "False"}, - }, + flags: map[string]model.Flag{ + "A": {Source: mockSource, DefaultVariant: "False"}, }, }, - expectedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + expectedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource, DefaultVariant: "False"}, }, }, @@ -286,25 +264,21 @@ func TestFlags_Update(t *testing.T) { }, { name: "Update multiple success", - storedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + storedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource, DefaultVariant: "True"}, "B": {Source: mockSource, DefaultVariant: "True"}, }, }, UpdateRequest: request{ source: mockSource, - flags: Flags{ - Flags: map[string]Flag{ - "A": {Source: mockSource, DefaultVariant: "False"}, - "B": {Source: mockSource, DefaultVariant: "False"}, - }, + flags: map[string]model.Flag{ + "A": {Source: mockSource, DefaultVariant: "False"}, + "B": {Source: mockSource, DefaultVariant: "False"}, }, }, - expectedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + expectedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource, DefaultVariant: "False"}, "B": {Source: mockSource, DefaultVariant: "False"}, }, @@ -313,23 +287,19 @@ func TestFlags_Update(t *testing.T) { }, { name: "Update success - conflict and override", - storedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + storedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource, DefaultVariant: "True"}, }, }, UpdateRequest: request{ source: mockOverrideSource, - flags: Flags{ - Flags: map[string]Flag{ - "A": {Source: mockOverrideSource, DefaultVariant: "True"}, - }, + flags: map[string]model.Flag{ + "A": {Source: mockOverrideSource, DefaultVariant: "True"}, }, }, - expectedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + expectedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockOverrideSource, DefaultVariant: "True"}, }, }, @@ -337,23 +307,19 @@ func TestFlags_Update(t *testing.T) { }, { name: "Update fail", - storedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + storedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource}, }, }, UpdateRequest: request{ source: mockSource, - flags: Flags{ - Flags: map[string]Flag{ - "B": {Source: mockSource}, - }, + flags: map[string]model.Flag{ + "B": {Source: mockSource}, }, }, - expectedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + expectedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource}, }, }, @@ -381,28 +347,24 @@ func TestFlags_Delete(t *testing.T) { tests := []struct { name string - storedState Flags - deleteRequest Flags - expectedState Flags + storedState *Flags + deleteRequest map[string]model.Flag + expectedState *Flags expectedNotificationKeys []string }{ { name: "Remove success", - storedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + storedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource}, "B": {Source: mockSource}, }, }, - deleteRequest: Flags{ - Flags: map[string]Flag{ - "A": {Source: mockSource}, - }, + deleteRequest: map[string]model.Flag{ + "A": {Source: mockSource}, }, - expectedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + expectedState: &Flags{ + Flags: map[string]model.Flag{ "B": {Source: mockSource}, }, }, @@ -410,21 +372,17 @@ func TestFlags_Delete(t *testing.T) { }, { name: "Nothing to remove", - storedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + storedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource}, "B": {Source: mockSource}, }, }, - deleteRequest: Flags{ - Flags: map[string]Flag{ - "C": {Source: mockSource}, - }, + deleteRequest: map[string]model.Flag{ + "C": {Source: mockSource}, }, - expectedState: Flags{ - mx: &sync.RWMutex{}, - Flags: map[string]Flag{ + expectedState: &Flags{ + Flags: map[string]model.Flag{ "A": {Source: mockSource}, "B": {Source: mockSource}, }, @@ -434,7 +392,7 @@ func TestFlags_Delete(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - messages := tt.storedState.Delete(mockLogger, mockSource, tt.deleteRequest) + messages := tt.storedState.DeleteFlags(mockLogger, mockSource, tt.deleteRequest) require.Equal(t, tt.storedState, tt.expectedState)