qontract-reconcile 0.10.2.dev25__py3-none-any.whl → 0.10.2.dev27__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qontract-reconcile
3
- Version: 0.10.2.dev25
3
+ Version: 0.10.2.dev27
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Project-URL: homepage, https://github.com/app-sre/qontract-reconcile
6
6
  Project-URL: repository, https://github.com/app-sre/qontract-reconcile
@@ -10,7 +10,7 @@ reconcile/aws_iam_password_reset.py,sha256=q96mwr2KeEQ5bpNniGlgIMZTxiuLSodcYfX-t
10
10
  reconcile/aws_support_cases_sos.py,sha256=hl_9L53yQYRQxKs3IWrd69Cc60XK067g_bJRM9B0udo,2975
11
11
  reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=O1wFp52EyF538c6txaWBs8eMtUIy19gyHZ6VzJ6QXS8,3512
12
12
  reconcile/checkpoint.py,sha256=_JhMxrye5BgkRMxWYuf7Upli6XayPINKSsuo3ynHTRc,5010
13
- reconcile/cli.py,sha256=jI3MMGWLBXYl-0aYOA4hLEQlBdP9lbb1FyGnqEjt-mM,107621
13
+ reconcile/cli.py,sha256=BCfKBc5BRq-9Y5WYe8sUoz9ufOsB1WwMvylx6mBf6CQ,107806
14
14
  reconcile/closedbox_endpoint_monitoring_base.py,sha256=MvGKBqH9PdHWdMjhLuptze-dk0Tifhp3-0SZdI-7Fmo,4862
15
15
  reconcile/cluster_deployment_mapper.py,sha256=5gumAaRCcFXsabUJ1dnuUy9WrP_FEEM5JnOnE8ch9sE,2326
16
16
  reconcile/dashdotdb_base.py,sha256=83ZWIf5JJk3P_D69y2TmXRcQr6ELJGlv10OM0h7fJVs,4767
@@ -37,7 +37,7 @@ reconcile/gitlab_mr_sqs_consumer.py,sha256=O46mdziPgGOndbU-0_UJKJVUaiEoVzJPEgKm4
37
37
  reconcile/gitlab_owners.py,sha256=Y3i-WqXhszLGqGmddt-tJ3OM2b_cViqDs1Ru1P1aQEM,13405
38
38
  reconcile/gitlab_permissions.py,sha256=gSGH6gAdJbPy5Z0rQGUqiNQSHty_tXQ_3Y4OQP_8nFs,8067
39
39
  reconcile/gitlab_projects.py,sha256=K3tFf_aD1W4Ijp5q-9Qek3kwFGEWPcZ1kd7tzFJ4GyQ,1781
40
- reconcile/integrations_manager.py,sha256=wZ5snv0l2KJ2EF7dAece8cDIvRYyGRXcdDcRfBNNfxQ,9573
40
+ reconcile/integrations_manager.py,sha256=CY7cOj5dzt2se4IOg11VQvGQ-eTvLML5Q42Z9SSgeSk,9463
41
41
  reconcile/jenkins_base.py,sha256=0Gocu3fU2YTltaxBlbDQOUvP-7CP2OSQV1ZRwtWeVXw,875
42
42
  reconcile/jenkins_job_builder.py,sha256=2aeOSS5pwKJgF4EzoHBWlOYNbzLj3qYzv6u55Qg6MAg,3466
43
43
  reconcile/jenkins_job_builds_cleaner.py,sha256=0iiX0iJiIIter0g9l0l-C6TUvUdVy8O9zFUh7kaYw5w,3865
@@ -46,7 +46,7 @@ reconcile/jenkins_roles.py,sha256=HadmoNhgOoKMFZJmCe_Gdg0_a9k_1MuOXidtr801nUU,45
46
46
  reconcile/jenkins_webhooks.py,sha256=dzMT1ywXjeAo5sHj-ittW06Ed3beAUPjnc_oCAtD-Rg,2150
47
47
  reconcile/jenkins_webhooks_cleaner.py,sha256=JsN_NVPfZJwv1JtSzZXDIHUqGiefL-DRffFnDGau9aY,1539
48
48
  reconcile/jenkins_worker_fleets.py,sha256=L2wEXpd4xuEHrXGss4iH788nG8UlLSYduZe1EY2IVw4,5377
49
- reconcile/jira_permissions_validator.py,sha256=Hp9wkBUXafSIGtXozVhYRiIh3lxXyObCnmB77mGA9ZQ,13419
49
+ reconcile/jira_permissions_validator.py,sha256=uuwzizb16jTWGctDB0cG0t9l3trDxZuK2SJA-9w7tfA,14872
50
50
  reconcile/jira_watcher.py,sha256=L_UL2MKm2SoIGNsCLThm28pnqCkoFc154JWsD6bURug,3593
51
51
  reconcile/ldap_users.py,sha256=7hdO5CAPl-VNBvDRmKHg13LoblHXXPt7YEKNGomAoGg,3158
52
52
  reconcile/mr_client_gateway.py,sha256=WhjMd-sIXDFCV8-rt8CEjurJ5OYB1pOD0K3o0tZRXQg,1885
@@ -215,7 +215,7 @@ reconcile/glitchtip_project_alerts/integration.py,sha256=BgMx-NyV9mTuv7Sotb2OioC
215
215
  reconcile/glitchtip_project_dsn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
216
216
  reconcile/glitchtip_project_dsn/integration.py,sha256=2iugub-kHYkHNK33n0v9_TeWonuxCPah_VkoTPvaajE,8077
217
217
  reconcile/gql_definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
218
- reconcile/gql_definitions/introspection.json,sha256=_MsmBFKNpf6uIymRHvkzVyz5ICgVD0aAcDi_7czXfxQ,2221786
218
+ reconcile/gql_definitions/introspection.json,sha256=GoGW7nosXPWvZYQsq6m8nYVIJHIO5hBh9upeFhS_M_k,2216702
219
219
  reconcile/gql_definitions/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
220
220
  reconcile/gql_definitions/acs/acs_instances.py,sha256=L91WW9LbhJbBSrECqShQpFtjoBOsmNIYLRpMbx1io5o,2181
221
221
  reconcile/gql_definitions/acs/acs_policies.py,sha256=bN5i4mks10Z23KJSj7jqp966Osq2dps4d-sPH9gjxEA,7008
@@ -335,7 +335,7 @@ reconcile/gql_definitions/glitchtip/glitchtip_project.py,sha256=AojrkCDGbVjY0TOk
335
335
  reconcile/gql_definitions/glitchtip_project_alerts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
336
336
  reconcile/gql_definitions/glitchtip_project_alerts/glitchtip_project.py,sha256=Pv6RcuIzpNmGc43eEq64nnKG0Dr7H0wjy5Xg1_oRltM,5197
337
337
  reconcile/gql_definitions/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
338
- reconcile/gql_definitions/integrations/integrations.py,sha256=yPpLVR_tML0QvjvLzGT7_D3SkpA6MaZqn0-45pukWgc,12821
338
+ reconcile/gql_definitions/integrations/integrations.py,sha256=HosEgRUlAkxLNoj2cnq3mrTdWDn9UvbNmtz6OcweIYk,11668
339
339
  reconcile/gql_definitions/jenkins_configs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
340
340
  reconcile/gql_definitions/jenkins_configs/jenkins_configs.py,sha256=kr1RwKcSpmpBkotl8rR0cOZ02Co5FAbE1he80CCFbTc,2961
341
341
  reconcile/gql_definitions/jenkins_configs/jenkins_instances.py,sha256=b3gYVzQavxeLe4jSM5ZxrO77Vvs7kOljVOXEkTO943U,2165
@@ -373,7 +373,6 @@ reconcile/gql_definitions/service_dependencies/jenkins_instance_fragment.py,sha2
373
373
  reconcile/gql_definitions/service_dependencies/service_dependencies.py,sha256=CpMq9KjhFA61yniLo_11ypVInoeMBXbNmcY7_VAep-0,4700
374
374
  reconcile/gql_definitions/sharding/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
375
375
  reconcile/gql_definitions/sharding/aws_accounts.py,sha256=CtvVUKlS6PfsbInXuhlNwoV4Zmo4uiOc0mwubED8JwQ,1968
376
- reconcile/gql_definitions/sharding/jira_boards.py,sha256=yZGLQEQef919UPpy7PWlfusegYv91pByH5jsumXsYuc,1685
377
376
  reconcile/gql_definitions/sharding/ocm_organization.py,sha256=duBIg3531yJ7fWniuHOG6mASgIiKfFIg6mef_lDzwnE,1871
378
377
  reconcile/gql_definitions/skupper_network/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
379
378
  reconcile/gql_definitions/skupper_network/site_controller_template.py,sha256=MFJTZAO0wRFlRBU4qfXUeQmCKIP4zkmnnCnrvX67FNY,756
@@ -581,7 +580,7 @@ reconcile/utils/external_resource_spec.py,sha256=bhH_xneFwATdFumTPkiQmcVKYI0gcaW
581
580
  reconcile/utils/external_resources.py,sha256=YzTb0xAcNdmKO326mGQy7BmST56CZcdru4lX7ai_7kw,7579
582
581
  reconcile/utils/filtering.py,sha256=S4PbMHuFr3ED0P2Q_ea5CAaB7FimI62B-F5YTaKrphA,402
583
582
  reconcile/utils/git.py,sha256=wzVIYAeKlMGW538U1mkJWUI6h_mFRUY4lawh2AR8hw4,2345
584
- reconcile/utils/github_api.py,sha256=oMInVIga9OITL7l5EWzs4lGXK0xAYhUznhjgXKjmmA0,2492
583
+ reconcile/utils/github_api.py,sha256=y3fxty7FKvfhdzfHgGSaIstL6A_Y2loUcMiyIK5TMDg,2750
585
584
  reconcile/utils/gitlab_api.py,sha256=SR0graae7UBZyLLKA7yGsOqKm7dXFe5cexcaa1SokQo,28486
586
585
  reconcile/utils/gpg.py,sha256=EKG7_fdMv8BMlV5yUdPiqoTx-KrzmVSEAl2sLkaKwWI,1123
587
586
  reconcile/utils/gql.py,sha256=C0thIm_k9MBldfqwHzyqtYZk9sIvMdm9IbbnXLGwjD8,14158
@@ -719,7 +718,7 @@ reconcile/utils/runtime/environment.py,sha256=h-CFKLK1qRl_gfOVIUwjqVNOmukIPzUG7A
719
718
  reconcile/utils/runtime/integration.py,sha256=H3kDfbc4IKrhubSDCWHs0W9oxM7FoCiPaxqA73c9aEs,11101
720
719
  reconcile/utils/runtime/meta.py,sha256=dWdKS9eHVuowFkTK4lgXJ723vS1y9giOMzePUKnHnDI,214
721
720
  reconcile/utils/runtime/runner.py,sha256=I30KRrX1UQbHc_Ir1cIZX3OfNSdoHKdnDSPAEB69Ilk,7944
722
- reconcile/utils/runtime/sharding.py,sha256=y7UZrikyCe-0LmQOCL32fvjqX4NrdZ5rUH9lpvKH5Ks,18517
721
+ reconcile/utils/runtime/sharding.py,sha256=r0ieUtNed7NvknSw6qQrCkKpVXE1shuHGnfFcnpA_k4,16142
723
722
  reconcile/utils/saasherder/__init__.py,sha256=3U8plqMAPRE1kjwZ5YnIsYsggTf4_gS7flRUEuXVBAs,343
724
723
  reconcile/utils/saasherder/interfaces.py,sha256=C2wrw34OXypshVocAsPrVZsSHptgw4g9u7Haa2wulZQ,9087
725
724
  reconcile/utils/saasherder/models.py,sha256=z8ln03zi2a8cu716NcNUDHp8Dv1VcVbhqdWVxCl7x9A,10148
@@ -767,7 +766,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
767
766
  tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
768
767
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
769
768
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
770
- qontract_reconcile-0.10.2.dev25.dist-info/METADATA,sha256=Iia7V8zNvzM3nF9KHAKkV8RG1MCrFVnDLiPHqq2Vfxw,24665
771
- qontract_reconcile-0.10.2.dev25.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
772
- qontract_reconcile-0.10.2.dev25.dist-info/entry_points.txt,sha256=JniHZPadNOILPyfSl0LF2YSp3Db7K2_W2CN7i9f3Gos,540
773
- qontract_reconcile-0.10.2.dev25.dist-info/RECORD,,
769
+ qontract_reconcile-0.10.2.dev27.dist-info/METADATA,sha256=Nr3x_-2IN6jO8C6UVvoDJmObBt8_Y6OZ54Ok0UsHikA,24665
770
+ qontract_reconcile-0.10.2.dev27.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
771
+ qontract_reconcile-0.10.2.dev27.dist-info/entry_points.txt,sha256=JniHZPadNOILPyfSl0LF2YSp3Db7K2_W2CN7i9f3Gos,540
772
+ qontract_reconcile-0.10.2.dev27.dist-info/RECORD,,
reconcile/cli.py CHANGED
@@ -1158,7 +1158,10 @@ def jenkins_webhooks_cleaner(ctx):
1158
1158
 
1159
1159
 
1160
1160
  @integration.command(short_help="Validate permissions in Jira.")
1161
- @click.option("--jira-board-name", help="The Jira board to act on.", default=None)
1161
+ @click.option(
1162
+ "--jira-board-name", help="The Jira board to act on.", default=None, multiple=True
1163
+ )
1164
+ @click.option("--board-check-interval", help="Check interval in minutes", default=120)
1162
1165
  @enable_extended_early_exit
1163
1166
  @extended_early_exit_cache_ttl_seconds
1164
1167
  @log_cached_log_output
@@ -1166,6 +1169,7 @@ def jenkins_webhooks_cleaner(ctx):
1166
1169
  def jira_permissions_validator(
1167
1170
  ctx,
1168
1171
  jira_board_name,
1172
+ board_check_interval,
1169
1173
  enable_extended_early_exit,
1170
1174
  extended_early_exit_cache_ttl_seconds,
1171
1175
  log_cached_log_output,
@@ -1176,6 +1180,7 @@ def jira_permissions_validator(
1176
1180
  reconcile.jira_permissions_validator,
1177
1181
  ctx.obj,
1178
1182
  jira_board_name=jira_board_name,
1183
+ board_check_interval=board_check_interval,
1179
1184
  enable_extended_early_exit=enable_extended_early_exit,
1180
1185
  extended_early_exit_cache_ttl_seconds=extended_early_exit_cache_ttl_seconds,
1181
1186
  log_cached_log_output=log_cached_log_output,
@@ -191,22 +191,6 @@ query Integrations {
191
191
  }
192
192
  }
193
193
  }
194
-
195
- ... on JiraBoardSharding_v1 {
196
- shardSpecOverrides {
197
- shard {
198
- name
199
- disable {
200
- integrations
201
- }
202
- }
203
- imageRef
204
- disabled
205
- resources {
206
- ... DeployResourcesFields
207
- }
208
- }
209
- }
210
194
  }
211
195
  }
212
196
  }
@@ -358,30 +342,10 @@ class CloudflareDNSZoneShardingV1(IntegrationShardingV1):
358
342
  shard_spec_overrides: Optional[list[CloudflareDNSZoneShardSpecOverrideV1]] = Field(..., alias="shardSpecOverrides")
359
343
 
360
344
 
361
- class DisableJiraBoardAutomationsV1(ConfiguredBaseModel):
362
- integrations: Optional[list[str]] = Field(..., alias="integrations")
363
-
364
-
365
- class JiraBoardV1(ConfiguredBaseModel):
366
- name: str = Field(..., alias="name")
367
- disable: Optional[DisableJiraBoardAutomationsV1] = Field(..., alias="disable")
368
-
369
-
370
- class JiraBoardShardSpecOverrideV1(ConfiguredBaseModel):
371
- shard: JiraBoardV1 = Field(..., alias="shard")
372
- image_ref: Optional[str] = Field(..., alias="imageRef")
373
- disabled: Optional[bool] = Field(..., alias="disabled")
374
- resources: Optional[DeployResourcesFields] = Field(..., alias="resources")
375
-
376
-
377
- class JiraBoardShardingV1(IntegrationShardingV1):
378
- shard_spec_overrides: Optional[list[JiraBoardShardSpecOverrideV1]] = Field(..., alias="shardSpecOverrides")
379
-
380
-
381
345
  class IntegrationManagedV1(ConfiguredBaseModel):
382
346
  namespace: NamespaceV1 = Field(..., alias="namespace")
383
347
  spec: IntegrationSpecV1 = Field(..., alias="spec")
384
- sharding: Optional[Union[StaticShardingV1, OpenshiftClusterShardingV1, OCMOrganizationShardingV1, AWSAccountShardingV1, CloudflareDNSZoneShardingV1, JiraBoardShardingV1, IntegrationShardingV1]] = Field(..., alias="sharding")
348
+ sharding: Optional[Union[StaticShardingV1, OpenshiftClusterShardingV1, OCMOrganizationShardingV1, AWSAccountShardingV1, CloudflareDNSZoneShardingV1, IntegrationShardingV1]] = Field(..., alias="sharding")
385
349
 
386
350
 
387
351
  class IntegrationV1(ConfiguredBaseModel):
@@ -29766,11 +29766,6 @@
29766
29766
  "kind": "OBJECT",
29767
29767
  "name": "AWSAccountSharding_v1",
29768
29768
  "ofType": null
29769
- },
29770
- {
29771
- "kind": "OBJECT",
29772
- "name": "JiraBoardSharding_v1",
29773
- "ofType": null
29774
29769
  }
29775
29770
  ]
29776
29771
  },
@@ -49774,122 +49769,6 @@
49774
49769
  "enumValues": null,
49775
49770
  "possibleTypes": null
49776
49771
  },
49777
- {
49778
- "kind": "OBJECT",
49779
- "name": "JiraBoardSharding_v1",
49780
- "description": null,
49781
- "fields": [
49782
- {
49783
- "name": "strategy",
49784
- "description": null,
49785
- "args": [],
49786
- "type": {
49787
- "kind": "NON_NULL",
49788
- "name": null,
49789
- "ofType": {
49790
- "kind": "SCALAR",
49791
- "name": "String",
49792
- "ofType": null
49793
- }
49794
- },
49795
- "isDeprecated": false,
49796
- "deprecationReason": null
49797
- },
49798
- {
49799
- "name": "shardSpecOverrides",
49800
- "description": null,
49801
- "args": [],
49802
- "type": {
49803
- "kind": "LIST",
49804
- "name": null,
49805
- "ofType": {
49806
- "kind": "NON_NULL",
49807
- "name": null,
49808
- "ofType": {
49809
- "kind": "OBJECT",
49810
- "name": "JiraBoardShardSpecOverride_v1",
49811
- "ofType": null
49812
- }
49813
- }
49814
- },
49815
- "isDeprecated": false,
49816
- "deprecationReason": null
49817
- }
49818
- ],
49819
- "inputFields": null,
49820
- "interfaces": [
49821
- {
49822
- "kind": "INTERFACE",
49823
- "name": "IntegrationSharding_v1",
49824
- "ofType": null
49825
- }
49826
- ],
49827
- "enumValues": null,
49828
- "possibleTypes": null
49829
- },
49830
- {
49831
- "kind": "OBJECT",
49832
- "name": "JiraBoardShardSpecOverride_v1",
49833
- "description": null,
49834
- "fields": [
49835
- {
49836
- "name": "shard",
49837
- "description": null,
49838
- "args": [],
49839
- "type": {
49840
- "kind": "NON_NULL",
49841
- "name": null,
49842
- "ofType": {
49843
- "kind": "OBJECT",
49844
- "name": "JiraBoard_v1",
49845
- "ofType": null
49846
- }
49847
- },
49848
- "isDeprecated": false,
49849
- "deprecationReason": null
49850
- },
49851
- {
49852
- "name": "imageRef",
49853
- "description": null,
49854
- "args": [],
49855
- "type": {
49856
- "kind": "SCALAR",
49857
- "name": "String",
49858
- "ofType": null
49859
- },
49860
- "isDeprecated": false,
49861
- "deprecationReason": null
49862
- },
49863
- {
49864
- "name": "resources",
49865
- "description": null,
49866
- "args": [],
49867
- "type": {
49868
- "kind": "OBJECT",
49869
- "name": "DeployResources_v1",
49870
- "ofType": null
49871
- },
49872
- "isDeprecated": false,
49873
- "deprecationReason": null
49874
- },
49875
- {
49876
- "name": "disabled",
49877
- "description": null,
49878
- "args": [],
49879
- "type": {
49880
- "kind": "SCALAR",
49881
- "name": "Boolean",
49882
- "ofType": null
49883
- },
49884
- "isDeprecated": false,
49885
- "deprecationReason": null
49886
- }
49887
- ],
49888
- "inputFields": null,
49889
- "interfaces": [],
49890
- "enumValues": null,
49891
- "possibleTypes": null
49892
- },
49893
49772
  {
49894
49773
  "kind": "OBJECT",
49895
49774
  "name": "StaticSubSharding_v1",
@@ -41,7 +41,6 @@ from reconcile.utils.runtime.sharding import (
41
41
  AWSAccountShardingStrategy,
42
42
  CloudflareDnsZoneShardingStrategy,
43
43
  IntegrationShardManager,
44
- JiraBoardShardingStrategy,
45
44
  OCMOrganizationShardingStrategy,
46
45
  OpenshiftClusterShardingStrategy,
47
46
  ShardSpec,
@@ -258,7 +257,6 @@ def run(
258
257
  OpenshiftClusterShardingStrategy.IDENTIFIER: OpenshiftClusterShardingStrategy(),
259
258
  CloudflareDnsZoneShardingStrategy.IDENTIFIER: CloudflareDnsZoneShardingStrategy(),
260
259
  OCMOrganizationShardingStrategy.IDENTIFIER: OCMOrganizationShardingStrategy(),
261
- JiraBoardShardingStrategy.IDENTIFIER: JiraBoardShardingStrategy(),
262
260
  },
263
261
  integration_runtime_meta=integration_runtime_meta,
264
262
  )
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  import sys
3
+ import time
3
4
  from collections.abc import Callable, Iterable
4
5
  from enum import IntFlag, auto
5
6
  from typing import Any, TypedDict
@@ -20,18 +21,21 @@ from reconcile.typed_queries.app_interface_vault_settings import (
20
21
  from reconcile.typed_queries.jira_settings import get_jira_settings
21
22
  from reconcile.typed_queries.jiralert_settings import get_jiralert_settings
22
23
  from reconcile.utils import gql, metrics
24
+ from reconcile.utils.defer import defer
23
25
  from reconcile.utils.disabled_integrations import integration_is_enabled
24
26
  from reconcile.utils.extended_early_exit import (
25
27
  ExtendedEarlyExitRunnerResult,
26
28
  extended_early_exit_run,
27
29
  )
28
30
  from reconcile.utils.jira_client import JiraClient, JiraWatcherSettings
31
+ from reconcile.utils.runtime.integration import DesiredStateShardConfig
29
32
  from reconcile.utils.secret_reader import SecretReaderBase, create_secret_reader
30
33
  from reconcile.utils.semver_helper import make_semver
34
+ from reconcile.utils.state import State, init_state
31
35
  from reconcile.utils.unleash import get_feature_toggle_state
32
36
 
33
37
  QONTRACT_INTEGRATION = "jira-permissions-validator"
34
- QONTRACT_INTEGRATION_VERSION = make_semver(1, 1, 0)
38
+ QONTRACT_INTEGRATION_VERSION = make_semver(1, 2, 0)
35
39
 
36
40
  NameToIdMap = dict[str, str]
37
41
 
@@ -66,6 +70,7 @@ class ValidationError(IntFlag):
66
70
 
67
71
  class RunnerParams(TypedDict):
68
72
  boards: list[JiraBoardV1]
73
+ board_check_interval: int
69
74
  dry_run: bool
70
75
 
71
76
 
@@ -201,12 +206,19 @@ def validate_boards(
201
206
  jira_boards: Iterable[JiraBoardV1],
202
207
  default_issue_type: str,
203
208
  default_reopen_state: str,
209
+ board_check_interval: int,
204
210
  dry_run: bool,
211
+ state: State,
205
212
  jira_client_class: type[JiraClient] = JiraClient,
206
213
  ) -> bool:
207
214
  error = False
208
215
  jira_clients: dict[str, JiraClient] = {}
209
216
  for board in jira_boards:
217
+ last_successful_run = state.get(board.name, 0)
218
+ if not dry_run and time.time() <= last_successful_run + board_check_interval:
219
+ logging.debug(f"[{board.name}] Skipping board")
220
+ continue
221
+
210
222
  logging.debug(f"[{board.name}] checking ...")
211
223
  if board.server.server_url not in jira_clients:
212
224
  jira_clients[board.server.server_url] = jira_client_class.create(
@@ -230,6 +242,9 @@ def validate_boards(
230
242
  case 0:
231
243
  # no errors
232
244
  logging.debug(f"[{board.name}] is valid")
245
+ if not dry_run:
246
+ # remember time of the last successful run
247
+ state[board.name] = time.time()
233
248
  case ValidationError.PERMISSION_ERROR:
234
249
  # we don't have all the permissions, but we can create jira tickets
235
250
  metrics_container.set_gauge(
@@ -257,13 +272,17 @@ def validate_boards(
257
272
 
258
273
 
259
274
  def get_jira_boards(
260
- query_func: Callable, jira_board_name: str | None = None
275
+ query_func: Callable,
276
+ jira_board_names: list[str] | None = None,
261
277
  ) -> list[JiraBoardV1]:
262
278
  return [
263
279
  board
264
280
  for board in query_jira_boards(query_func=query_func).jira_boards or []
265
281
  if integration_is_enabled(QONTRACT_INTEGRATION, board)
266
- and (not jira_board_name or board.name.lower() == jira_board_name.lower())
282
+ and (
283
+ not jira_board_names
284
+ or board.name.lower() in [name.lower() for name in jira_board_names]
285
+ )
267
286
  ]
268
287
 
269
288
 
@@ -273,16 +292,18 @@ def export_boards(boards: list[JiraBoardV1]) -> list[dict]:
273
292
 
274
293
  def run(
275
294
  dry_run: bool,
276
- jira_board_name: str | None = None,
295
+ jira_board_name: list[str] | None = None,
296
+ board_check_interval: int = 3600,
277
297
  enable_extended_early_exit: bool = False,
278
298
  extended_early_exit_cache_ttl_seconds: int = 3600,
279
299
  log_cached_log_output: bool = False,
280
300
  ) -> None:
281
301
  gql_api = gql.get_api()
282
- boards = get_jira_boards(query_func=gql_api.query, jira_board_name=jira_board_name)
302
+ boards = get_jira_boards(query_func=gql_api.query, jira_board_names=jira_board_name)
283
303
  runner_params: RunnerParams = {
284
304
  "boards": boards,
285
305
  "dry_run": dry_run,
306
+ "board_check_interval": board_check_interval,
286
307
  }
287
308
  if enable_extended_early_exit and get_feature_toggle_state(
288
309
  "jira-permissions-validator-extended-early-exit",
@@ -300,7 +321,7 @@ def run(
300
321
  # don't use `dry_run` in the cache key because this is a read-only integration
301
322
  dry_run=False,
302
323
  cache_source=cache_source,
303
- shard=jira_board_name or "",
324
+ shard="_".join(set(jira_board_name)) if jira_board_name else "",
304
325
  ttl_seconds=extended_early_exit_cache_ttl_seconds,
305
326
  logger=logging.getLogger(),
306
327
  runner=runner,
@@ -312,12 +333,21 @@ def run(
312
333
  runner(**runner_params)
313
334
 
314
335
 
315
- def runner(boards: list[JiraBoardV1], dry_run: bool) -> ExtendedEarlyExitRunnerResult:
336
+ @defer
337
+ def runner(
338
+ boards: list[JiraBoardV1],
339
+ dry_run: bool,
340
+ board_check_interval: int,
341
+ defer: Callable | None = None,
342
+ ) -> ExtendedEarlyExitRunnerResult:
316
343
  gql_api = gql.get_api()
317
344
  settings = get_jira_settings(gql_api=gql_api)
318
345
  jiralert_settings = get_jiralert_settings(query_func=gql_api.query)
319
346
  vault_settings = get_app_interface_vault_settings()
320
347
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)
348
+ state = init_state(integration=QONTRACT_INTEGRATION, secret_reader=secret_reader)
349
+ if defer:
350
+ defer(state.cleanup)
321
351
 
322
352
  with metrics.transactional_metrics("jira-boards") as metrics_container:
323
353
  error = validate_boards(
@@ -327,7 +357,9 @@ def runner(boards: list[JiraBoardV1], dry_run: bool) -> ExtendedEarlyExitRunnerR
327
357
  jira_boards=boards,
328
358
  default_issue_type=jiralert_settings.default_issue_type,
329
359
  default_reopen_state=jiralert_settings.default_reopen_state,
360
+ board_check_interval=board_check_interval,
330
361
  dry_run=dry_run,
362
+ state=state,
331
363
  )
332
364
 
333
365
  if error:
@@ -337,13 +369,22 @@ def runner(boards: list[JiraBoardV1], dry_run: bool) -> ExtendedEarlyExitRunnerR
337
369
 
338
370
 
339
371
  def early_exit_desired_state(
340
- *args: Any, jira_board_name: str | None = None, **kwargs: Any
372
+ *args: Any, jira_board_name: list[str] | None = None, **kwargs: Any
341
373
  ) -> dict[str, Any]:
342
374
  return {
343
375
  "boards": export_boards(
344
376
  get_jira_boards(
345
377
  query_func=gql.get_api().query,
346
- jira_board_name=jira_board_name,
378
+ jira_board_names=jira_board_name,
347
379
  )
348
380
  )
349
381
  }
382
+
383
+
384
+ def desired_state_shard_config() -> DesiredStateShardConfig:
385
+ return DesiredStateShardConfig(
386
+ shard_arg_name="jira_board_name",
387
+ shard_path_selectors={"boards[*].name"},
388
+ shard_arg_is_collection=True,
389
+ sharded_run_review=lambda proposal: len(proposal.proposed_shards) <= 2,
390
+ )
@@ -3,11 +3,7 @@ from pathlib import Path
3
3
  from types import TracebackType
4
4
  from urllib.parse import urlparse
5
5
 
6
- from github import (
7
- Commit,
8
- Github,
9
- UnknownObjectException,
10
- )
6
+ from github import Commit, Github, GithubException, UnknownObjectException
11
7
  from sretoolbox.utils import retry
12
8
 
13
9
  GH_BASE_URL = os.environ.get("GITHUB_API", "https://api.github.com")
@@ -75,6 +71,13 @@ class GithubRepositoryApi:
75
71
  # -> for now staying backwards compatible
76
72
  return None
77
73
  return content.decoded_content
74
+ except GithubException as e:
75
+ # handling a bug in the upstream GH library
76
+ # https://github.com/PyGithub/PyGithub/issues/3179
77
+ if e.status == 404:
78
+ return None
79
+ else:
80
+ raise e
78
81
  except UnknownObjectException:
79
82
  return None
80
83
 
@@ -19,8 +19,6 @@ from reconcile.gql_definitions.integrations.integrations import (
19
19
  IntegrationManagedV1,
20
20
  IntegrationShardingV1,
21
21
  IntegrationSpecV1,
22
- JiraBoardShardingV1,
23
- JiraBoardShardSpecOverrideV1,
24
22
  OCMOrganizationShardingV1,
25
23
  OCMOrganizationShardSpecOverrideV1,
26
24
  OpenshiftClusterShardingV1,
@@ -30,7 +28,6 @@ from reconcile.gql_definitions.integrations.integrations import (
30
28
  SubShardingV1,
31
29
  )
32
30
  from reconcile.gql_definitions.sharding import aws_accounts as sharding_aws_accounts
33
- from reconcile.gql_definitions.sharding import jira_boards as sharding_jira_boards
34
31
  from reconcile.gql_definitions.sharding import (
35
32
  ocm_organization as sharding_ocm_organization,
36
33
  )
@@ -437,67 +434,6 @@ class CloudflareDnsZoneShardingStrategy:
437
434
  return shards
438
435
 
439
436
 
440
- class JiraBoardShardingStrategy:
441
- IDENTIFIER = "per-jira-board"
442
-
443
- def __init__(
444
- self,
445
- jira_boards: Iterable[sharding_jira_boards.JiraBoardV1] | None = None,
446
- ):
447
- if not jira_boards:
448
- self.jira_boards = (
449
- sharding_jira_boards.query(query_func=gql.get_api().query).jira_boards
450
- or []
451
- )
452
- else:
453
- self.jira_boards = list(jira_boards)
454
-
455
- def get_shard_spec_overrides(
456
- self, sharding: IntegrationShardingV1 | None
457
- ) -> dict[str, JiraBoardShardSpecOverrideV1]:
458
- spos: dict[str, JiraBoardShardSpecOverrideV1] = {}
459
-
460
- if isinstance(sharding, JiraBoardShardingV1) and sharding.shard_spec_overrides:
461
- for sp in sharding.shard_spec_overrides or []:
462
- spos[sp.shard.name] = sp
463
- return spos
464
-
465
- def check_integration_sharding_params(self, meta: IntegrationMeta) -> None:
466
- if "--jira-board-name" not in meta.args:
467
- raise ValueError(
468
- f"the integration {meta.name} does not support the required argument "
469
- " --jira-board-name for the 'per-jira-board' sharding strategy."
470
- )
471
-
472
- def build_shard_spec(
473
- self,
474
- jira_board: sharding_jira_boards.JiraBoardV1,
475
- integration_spec: IntegrationSpecV1,
476
- spo: JiraBoardShardSpecOverrideV1 | None,
477
- ) -> ShardSpec:
478
- return ShardSpec(
479
- shard_key=jira_board.name,
480
- shard_name_suffix=f"-{jira_board.name.lower()}",
481
- extra_args=(integration_spec.extra_args or "")
482
- + f" --jira-board-name {jira_board.name}",
483
- shard_spec_overrides=spo,
484
- )
485
-
486
- def build_integration_shards(
487
- self,
488
- integration_meta: IntegrationMeta,
489
- integration_managed: IntegrationManagedV1,
490
- ) -> list[ShardSpec]:
491
- self.check_integration_sharding_params(integration_meta)
492
- spos = self.get_shard_spec_overrides(integration_managed.sharding)
493
- shards = []
494
- for board in self.jira_boards:
495
- spo = spos.get(board.name)
496
- base_shard = self.build_shard_spec(board, integration_managed.spec, spo)
497
- shards.append(base_shard)
498
- return shards
499
-
500
-
501
437
  @dataclass
502
438
  class IntegrationShardManager:
503
439
  strategies: dict[str, ShardingStrategy]
@@ -1,60 +0,0 @@
1
- """
2
- Generated by qenerate plugin=pydantic_v1. DO NOT MODIFY MANUALLY!
3
- """
4
- from collections.abc import Callable # noqa: F401 # pylint: disable=W0611
5
- from datetime import datetime # noqa: F401 # pylint: disable=W0611
6
- from enum import Enum # noqa: F401 # pylint: disable=W0611
7
- from typing import ( # noqa: F401 # pylint: disable=W0611
8
- Any,
9
- Optional,
10
- Union,
11
- )
12
-
13
- from pydantic import ( # noqa: F401 # pylint: disable=W0611
14
- BaseModel,
15
- Extra,
16
- Field,
17
- Json,
18
- )
19
-
20
-
21
- DEFINITION = """
22
- query JiraBoardSharding {
23
- jira_boards: jira_boards_v1 {
24
- name
25
- }
26
- }
27
- """
28
-
29
-
30
- class ConfiguredBaseModel(BaseModel):
31
- class Config:
32
- smart_union=True
33
- extra=Extra.forbid
34
-
35
-
36
- class JiraBoardV1(ConfiguredBaseModel):
37
- name: str = Field(..., alias="name")
38
-
39
-
40
- class JiraBoardShardingQueryData(ConfiguredBaseModel):
41
- jira_boards: Optional[list[JiraBoardV1]] = Field(..., alias="jira_boards")
42
-
43
-
44
- def query(query_func: Callable, **kwargs: Any) -> JiraBoardShardingQueryData:
45
- """
46
- This is a convenience function which queries and parses the data into
47
- concrete types. It should be compatible with most GQL clients.
48
- You do not have to use it to consume the generated data classes.
49
- Alternatively, you can also mime and alternate the behavior
50
- of this function in the caller.
51
-
52
- Parameters:
53
- query_func (Callable): Function which queries your GQL Server
54
- kwargs: optional arguments that will be passed to the query function
55
-
56
- Returns:
57
- JiraBoardShardingQueryData: queried data parsed into generated classes
58
- """
59
- raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
60
- return JiraBoardShardingQueryData(**raw_data)