qontract-reconcile 0.10.1rc834__py3-none-any.whl → 0.10.1rc835__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.1
2
2
  Name: qontract-reconcile
3
- Version: 0.10.1rc834
3
+ Version: 0.10.1rc835
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Home-page: https://github.com/app-sre/qontract-reconcile
6
6
  Author: Red Hat App-SRE Team
@@ -10,7 +10,7 @@ reconcile/aws_iam_password_reset.py,sha256=NwErtrqgBiXr7eGCAHdtGGOx0S7-4JnSc29Ie
10
10
  reconcile/aws_support_cases_sos.py,sha256=Jk6_XjDeJSYxgRGqcEAOcynt9qJF2r5HPIPcSKmoBv8,2974
11
11
  reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=W_VJagnsJR1v5oqjlI3RJJE0_nhtJ0m81RS8zWA5u5c,3538
12
12
  reconcile/checkpoint.py,sha256=R2WFXUXLTB4sWMi4GeA4eegsuf_1-Q4vH8M0Toh3Ij4,5036
13
- reconcile/cli.py,sha256=bRYr5vnUstP3dkRmt3NOyCaA4n-1xIxQU6vooISgFTk,102404
13
+ reconcile/cli.py,sha256=9k1FKxkGO8yLB_-J3juHg6MoM3wTtHv7TOHLKh51t0k,103241
14
14
  reconcile/closedbox_endpoint_monitoring_base.py,sha256=SMhkcQqprWvThrIJa3U_3uh5w1h-alleW1QnCJFY4Qw,4909
15
15
  reconcile/cluster_deployment_mapper.py,sha256=2Ah-nu-Mdig0pjuiZl_XLrmVAjYzFjORR3dMlCgkmw0,2352
16
16
  reconcile/dashdotdb_base.py,sha256=R2JuwiXAEYAFiCtnztM_IIr1rtVzPpaWAmgxuDa2FgY,4813
@@ -238,6 +238,7 @@ reconcile/gql_definitions/common/app_interface_dms_settings.py,sha256=h-N7-XGpmH
238
238
  reconcile/gql_definitions/common/app_interface_repo_settings.py,sha256=rud0rz9NIFF-h1fFdk3MnwGmx73rhwrn1taN_HefvyU,1754
239
239
  reconcile/gql_definitions/common/app_interface_state_settings.py,sha256=VXIK0Hmyv6GTShI86IGkjxyHGwufqUBAh617XKUAKaI,2507
240
240
  reconcile/gql_definitions/common/app_interface_vault_settings.py,sha256=w8quvdG0cSq71ZyJokPPp7MyMpoDb6-HLQ3o9JHVGRQ,1771
241
+ reconcile/gql_definitions/common/aws_vpc_requests.py,sha256=BBBQLiLRBuvkXdNFN_uyZ_kyvmroziHByyJBH6X0oCA,2269
241
242
  reconcile/gql_definitions/common/aws_vpcs.py,sha256=Dss9dQ3xagnz3Ltg1e9mtG2PAmQGBbUzKCmmzvuN28s,1892
242
243
  reconcile/gql_definitions/common/clusters.py,sha256=eJIbMQltj-Sc7h_QVIeFkbRy1b_QJIkHATfbagxM-yU,21527
243
244
  reconcile/gql_definitions/common/clusters_minimal.py,sha256=JYrJV_aStmryiiGKyiXhj47qpF_8KilCqy-d9CofBCo,4635
@@ -280,6 +281,8 @@ reconcile/gql_definitions/fragments/aws_account_managed.py,sha256=zXbux0Bb7QZ37f
280
281
  reconcile/gql_definitions/fragments/aws_account_sso.py,sha256=ITR3PLz4Iq1SiWAoYGWPDuHJnAmTyZ0QQqs2Zsi8pxA,979
281
282
  reconcile/gql_definitions/fragments/aws_infra_management_account.py,sha256=uAmALVRF2gBM3p_Dmez_ew6KVAtetamwOPkRIPZAlGc,1254
282
283
  reconcile/gql_definitions/fragments/aws_vpc.py,sha256=T2egTwi2Rb0IRBBmsyag8xKpu_m6GbIAy80fhZNZwk8,1434
284
+ reconcile/gql_definitions/fragments/aws_vpc_request.py,sha256=KleRM5asZJGcrUjW5LzUE5vyRFmL-3dVakdnyXNdYKc,2046
285
+ reconcile/gql_definitions/fragments/aws_vpc_request_subnet.py,sha256=qaTFT8cGzEslw51nUeb45Nfnv6kFxUm4CWrRR3xfBvA,760
283
286
  reconcile/gql_definitions/fragments/deplopy_resources.py,sha256=0u3xYqL5NpMf149BJLfPhHqAOWu06aLULdNk_2Mulxg,1089
284
287
  reconcile/gql_definitions/fragments/disable.py,sha256=Ojw98OSxcovrtmw_aAyhaVHhIa1MSUbBfKX4i2IpI74,715
285
288
  reconcile/gql_definitions/fragments/jumphost_common_fields.py,sha256=FOqqDRqzPdt4ZNTRRZPFYCKE1HIa1ATF0u0NsdZzyaw,1034
@@ -463,6 +466,10 @@ reconcile/terraform_init/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
463
466
  reconcile/terraform_init/integration.py,sha256=xcFKTc_or3xB3kE_I3OECNkkgbwALIwwdiktnF-MyWI,6114
464
467
  reconcile/terraform_init/merge_request.py,sha256=3CYtgSd7Q9zjKg4wsDz437EPCRfGeZZ8fZ0Y-ChKXJY,1475
465
468
  reconcile/terraform_init/merge_request_manager.py,sha256=fMcT6hbdEF3nFATJpvr8BedvQHq_MzFkgVJSloBNwOQ,3101
469
+ reconcile/terraform_vpc_resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
470
+ reconcile/terraform_vpc_resources/integration.py,sha256=eyIo2hJC2Uv6H48dZp5-P7HS5IyrU1upVQOVJtDzP18,7862
471
+ reconcile/terraform_vpc_resources/merge_request.py,sha256=loRymUigCIvaaT0s_NzktZchh-DGRQnCICdBSCAcFPY,1503
472
+ reconcile/terraform_vpc_resources/merge_request_manager.py,sha256=Vj2nuQbQyrL4q_il1My-bLxYNh_r3YXqX45P8fwtP6Q,3259
466
473
  reconcile/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
467
474
  reconcile/test/conftest.py,sha256=0pO4UxFeBALKbL9gwemyap0VkbPR8n5TtZbf5c9pSv0,4303
468
475
  reconcile/test/fixtures.py,sha256=9SDWAUlSd1rCx7z3GhULHcpr-I6FyCsXxaFAZIqYQsQ,591
@@ -571,6 +578,7 @@ reconcile/typed_queries/app_interface_deadmanssnitch_settings.py,sha256=y30_SmS0
571
578
  reconcile/typed_queries/app_interface_repo_url.py,sha256=ePr_nvjDeyZPR4sHsDQFubV2gslj9lPUSzIWWhm0syo,609
572
579
  reconcile/typed_queries/app_interface_state_settings.py,sha256=ytNAf3feg8JwmdCC4vO1WtVb-Ok5_ZtNAL3mXe2d1Pw,479
573
580
  reconcile/typed_queries/app_interface_vault_settings.py,sha256=uFS7qLjjGnrK8gm2fkUT8RFd7s_5kxTWWpxZRhr1Ndo,746
581
+ reconcile/typed_queries/aws_vpc_requests.py,sha256=c3nNVapucJR4AtZuUfcVoynSSLb_hLlsDkjQsP1qD98,390
574
582
  reconcile/typed_queries/aws_vpcs.py,sha256=bM7dWpVHV6J1WYjgHOe2rfAl4vKpUoC8khPQSdtkxVQ,371
575
583
  reconcile/typed_queries/clusters.py,sha256=tptLP2Ov1lNqtDGuPKBOVvpDtREm5CQFDDVyp0xKkRQ,506
576
584
  reconcile/typed_queries/clusters_minimal.py,sha256=flvs4oXVR4b971h-zMTK3C2GL3bjMnvZgrAeI3XgBQQ,517
@@ -632,7 +640,7 @@ reconcile/utils/git_secrets.py,sha256=0wGNL5mvDtVPRuu3vEQgld1Am64gIDJHtmu1_ZKxMA
632
640
  reconcile/utils/github_api.py,sha256=_bttNxYKeam_tLVe27L7O4gKqSn6CeyuFnJn8tSaUVY,2488
633
641
  reconcile/utils/gitlab_api.py,sha256=9C6oS98w1z-pDAAz5k9kgEvRO6r7hduHoOl7cufFr5s,27400
634
642
  reconcile/utils/gpg.py,sha256=EKG7_fdMv8BMlV5yUdPiqoTx-KrzmVSEAl2sLkaKwWI,1123
635
- reconcile/utils/gql.py,sha256=bzIYYYYGIO_4Db4sA-mnZUOPVNBELZD5EcIUSTbYG1o,13604
643
+ reconcile/utils/gql.py,sha256=qMWj39ilJGV1YAeUvbqVYq6u9zxuPvZqxFLaLb6A-bA,14256
636
644
  reconcile/utils/grouping.py,sha256=kWKivD14eAkiDneH_VIl_XyUdcVVQgiaKA9sLsuD2dw,441
637
645
  reconcile/utils/helm.py,sha256=Rt1ao4yI4GdThZyIPV75JLJjkvu9d8kOLKQht6KUzyE,1325
638
646
  reconcile/utils/helpers.py,sha256=k9svgFFZG7H5FvHYY0g5jJyvgvh2UDZxf0Ib221teag,1179
@@ -679,7 +687,7 @@ reconcile/utils/state.py,sha256=DRxzuOw3Iha-b2esBMjZTZ5K-OxfrGbkyEvpTT5xHTs,1636
679
687
  reconcile/utils/structs.py,sha256=LcbLEg8WxfRqM6nW7NhcWN0YeqF7SQzxOgntmLs1SgY,352
680
688
  reconcile/utils/template.py,sha256=wTvRU4AnAV_o042tD4Mwls2dwWMuk7MKnde3MaCjaYg,331
681
689
  reconcile/utils/terraform_client.py,sha256=mZEKpu6nbfiQd60wRkc8-5sljBTUTOgaAKnF89itMzU,32085
682
- reconcile/utils/terrascript_aws_client.py,sha256=VlvIHgrZRiMFVgx6a8ZHxoiJoDwqbtOmsZFnwwNrdL0,273199
690
+ reconcile/utils/terrascript_aws_client.py,sha256=87J2RszhfqYkABzZ5yO-JxycOvjvrx3bWgSmISop1tQ,276092
683
691
  reconcile/utils/three_way_diff_strategy.py,sha256=nnTk4VDex1GIEqryFVfATU1AeRwzXyoPl_FX6zzmRFI,4643
684
692
  reconcile/utils/throughput.py,sha256=iP4UWAe2LVhDo69mPPmgo9nQ7RxHD6_GS8MZe-aSiuM,344
685
693
  reconcile/utils/vault.py,sha256=AYGG5aDJ7CSVhTFdZowfEg3iSQWenoAt676aGjHQMX8,14978
@@ -812,8 +820,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
812
820
  tools/test/test_qontract_cli.py,sha256=_D61RFGAN5x44CY1tYbouhlGXXABwYfxKSWSQx3Jrss,4941
813
821
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
814
822
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
815
- qontract_reconcile-0.10.1rc834.dist-info/METADATA,sha256=GwMy-WLj9_4L9kW4KPlgRfD7XUInbC5v6Kn7gHcOVNg,2275
816
- qontract_reconcile-0.10.1rc834.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
817
- qontract_reconcile-0.10.1rc834.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
818
- qontract_reconcile-0.10.1rc834.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
819
- qontract_reconcile-0.10.1rc834.dist-info/RECORD,,
823
+ qontract_reconcile-0.10.1rc835.dist-info/METADATA,sha256=FvhcaquoyfUx-ny_1HtDquApeBVedhJNdqbWpmbmCc4,2275
824
+ qontract_reconcile-0.10.1rc835.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
825
+ qontract_reconcile-0.10.1rc835.dist-info/entry_points.txt,sha256=GKQqCl2j2X1BJQ69een6rHcR26PmnxnONLNOQB-nRjY,491
826
+ qontract_reconcile-0.10.1rc835.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
827
+ qontract_reconcile-0.10.1rc835.dist-info/RECORD,,
reconcile/cli.py CHANGED
@@ -2287,6 +2287,35 @@ def terraform_users(
2287
2287
  )
2288
2288
 
2289
2289
 
2290
+ @integration.command(short_help="Manage VPC creation")
2291
+ @binary(["terraform"])
2292
+ @binary_version("terraform", ["version"], TERRAFORM_VERSION_REGEX, TERRAFORM_VERSION)
2293
+ @account_name
2294
+ @print_to_file
2295
+ @threaded()
2296
+ @enable_deletion(default=False)
2297
+ @click.pass_context
2298
+ def terraform_vpc_resources(
2299
+ ctx, account_name, print_to_file, thread_pool_size, enable_deletion
2300
+ ):
2301
+ from reconcile.terraform_vpc_resources.integration import (
2302
+ TerraformVpcResources,
2303
+ TerraformVpcResourcesParams,
2304
+ )
2305
+
2306
+ run_class_integration(
2307
+ TerraformVpcResources(
2308
+ TerraformVpcResourcesParams(
2309
+ account_name=account_name,
2310
+ print_to_file=print_to_file,
2311
+ thread_pool_size=thread_pool_size,
2312
+ enable_deletion=enable_deletion,
2313
+ )
2314
+ ),
2315
+ ctx.obj,
2316
+ )
2317
+
2318
+
2290
2319
  @integration.command(
2291
2320
  short_help="Manage VPC peerings between OSD clusters and AWS accounts or other OSD clusters."
2292
2321
  )
@@ -0,0 +1,102 @@
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
+ from reconcile.gql_definitions.fragments.aws_vpc_request import VPCRequest
21
+
22
+
23
+ DEFINITION = """
24
+ fragment TerraformState on TerraformStateAWS_v1 {
25
+ provider
26
+ bucket
27
+ region
28
+ integrations {
29
+ key
30
+ integration
31
+ }
32
+ }
33
+
34
+ fragment VPCRequest on VPCRequest_v1 {
35
+ identifier
36
+ account {
37
+ name
38
+ uid
39
+ terraformUsername
40
+ automationToken {
41
+ ...VaultSecret
42
+ }
43
+ supportedDeploymentRegions
44
+ resourcesDefaultRegion
45
+ providerVersion
46
+ terraformState {
47
+ ...TerraformState
48
+ }
49
+ }
50
+ region
51
+ cidr_block {
52
+ networkAddress
53
+ }
54
+ subnets {
55
+ private
56
+ public
57
+ availability_zones
58
+ }
59
+ }
60
+
61
+ fragment VaultSecret on VaultSecret_v1 {
62
+ path
63
+ field
64
+ version
65
+ format
66
+ }
67
+
68
+ query VPCRequest {
69
+ vpc_requests: aws_vpc_request_v1 {
70
+ ...VPCRequest
71
+ }
72
+ }
73
+ """
74
+
75
+
76
+ class ConfiguredBaseModel(BaseModel):
77
+ class Config:
78
+ smart_union=True
79
+ extra=Extra.forbid
80
+
81
+
82
+ class VPCRequestQueryData(ConfiguredBaseModel):
83
+ vpc_requests: Optional[list[VPCRequest]] = Field(..., alias="vpc_requests")
84
+
85
+
86
+ def query(query_func: Callable, **kwargs: Any) -> VPCRequestQueryData:
87
+ """
88
+ This is a convenience function which queries and parses the data into
89
+ concrete types. It should be compatible with most GQL clients.
90
+ You do not have to use it to consume the generated data classes.
91
+ Alternatively, you can also mime and alternate the behavior
92
+ of this function in the caller.
93
+
94
+ Parameters:
95
+ query_func (Callable): Function which queries your GQL Server
96
+ kwargs: optional arguments that will be passed to the query function
97
+
98
+ Returns:
99
+ VPCRequestQueryData: queried data parsed into generated classes
100
+ """
101
+ raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
102
+ return VPCRequestQueryData(**raw_data)
@@ -0,0 +1,56 @@
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
+ from reconcile.gql_definitions.fragments.terraform_state import TerraformState
21
+ from reconcile.gql_definitions.fragments.vault_secret import VaultSecret
22
+
23
+
24
+ class ConfiguredBaseModel(BaseModel):
25
+ class Config:
26
+ smart_union=True
27
+ extra=Extra.forbid
28
+
29
+
30
+ class AWSAccountV1(ConfiguredBaseModel):
31
+ name: str = Field(..., alias="name")
32
+ uid: str = Field(..., alias="uid")
33
+ terraform_username: Optional[str] = Field(..., alias="terraformUsername")
34
+ automation_token: VaultSecret = Field(..., alias="automationToken")
35
+ supported_deployment_regions: Optional[list[str]] = Field(..., alias="supportedDeploymentRegions")
36
+ resources_default_region: str = Field(..., alias="resourcesDefaultRegion")
37
+ provider_version: str = Field(..., alias="providerVersion")
38
+ terraform_state: Optional[TerraformState] = Field(..., alias="terraformState")
39
+
40
+
41
+ class NetworkV1(ConfiguredBaseModel):
42
+ network_address: str = Field(..., alias="networkAddress")
43
+
44
+
45
+ class VPCRequestSubnetsListsV1(ConfiguredBaseModel):
46
+ private: Optional[list[str]] = Field(..., alias="private")
47
+ public: Optional[list[str]] = Field(..., alias="public")
48
+ availability_zones: Optional[list[str]] = Field(..., alias="availability_zones")
49
+
50
+
51
+ class VPCRequest(ConfiguredBaseModel):
52
+ identifier: str = Field(..., alias="identifier")
53
+ account: AWSAccountV1 = Field(..., alias="account")
54
+ region: str = Field(..., alias="region")
55
+ cidr_block: NetworkV1 = Field(..., alias="cidr_block")
56
+ subnets: Optional[VPCRequestSubnetsListsV1] = Field(..., alias="subnets")
@@ -0,0 +1,29 @@
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
+ class ConfiguredBaseModel(BaseModel):
22
+ class Config:
23
+ smart_union=True
24
+ extra=Extra.forbid
25
+
26
+
27
+ class VPCRequestSubnet(ConfiguredBaseModel):
28
+ availability_zone: str = Field(..., alias="availability_zone")
29
+ cidr_block: str = Field(..., alias="cidr_block")
File without changes
@@ -0,0 +1,214 @@
1
+ import logging
2
+ import sys
3
+ from collections.abc import Mapping, MutableMapping
4
+ from typing import Any, Iterable, Optional
5
+
6
+ import jinja2
7
+
8
+ from reconcile.gql_definitions.fragments.aws_vpc_request import (
9
+ AWSAccountV1,
10
+ VPCRequest,
11
+ )
12
+ from reconcile.status import ExitCodes
13
+ from reconcile.terraform_vpc_resources.merge_request import Renderer, create_parser
14
+ from reconcile.terraform_vpc_resources.merge_request_manager import (
15
+ MergeRequestManager,
16
+ MrData,
17
+ )
18
+ from reconcile.typed_queries.app_interface_repo_url import get_app_interface_repo_url
19
+ from reconcile.typed_queries.app_interface_vault_settings import (
20
+ get_app_interface_vault_settings,
21
+ )
22
+ from reconcile.typed_queries.aws_vpc_requests import get_aws_vpc_requests
23
+ from reconcile.typed_queries.github_orgs import get_github_orgs
24
+ from reconcile.typed_queries.gitlab_instances import get_gitlab_instances
25
+ from reconcile.utils import gql
26
+ from reconcile.utils.runtime.integration import (
27
+ DesiredStateShardConfig,
28
+ PydanticRunParams,
29
+ QontractReconcileIntegration,
30
+ )
31
+ from reconcile.utils.secret_reader import create_secret_reader
32
+ from reconcile.utils.semver_helper import make_semver
33
+ from reconcile.utils.terraform_client import TerraformClient
34
+ from reconcile.utils.terrascript_aws_client import TerrascriptClient
35
+ from reconcile.utils.vcs import VCS
36
+
37
+ QONTRACT_INTEGRATION = "terraform_vpc_resources"
38
+ QONTRACT_INTEGRATION_VERSION = make_semver(0, 1, 0)
39
+ QONTRACT_TF_PREFIX = "qrtvr"
40
+ AWS_PROVIDER_VERSION = "5.7.1"
41
+
42
+
43
+ class TerraformVpcResourcesParams(PydanticRunParams):
44
+ account_name: Optional[str]
45
+ print_to_file: Optional[str]
46
+ thread_pool_size: int
47
+ enable_deletion: bool = False
48
+
49
+
50
+ class NoManagedVPCForAccount(Exception):
51
+ pass
52
+
53
+
54
+ class TerraformVpcResources(QontractReconcileIntegration[TerraformVpcResourcesParams]):
55
+ @property
56
+ def name(self) -> str:
57
+ return QONTRACT_INTEGRATION.replace("_", "-")
58
+
59
+ def _filter_accounts(
60
+ self, data: Iterable[VPCRequest], account_name: Optional[str]
61
+ ) -> list[AWSAccountV1]:
62
+ """Return a list of accounts extracted from the provided VPCRequests.
63
+ If account_name is given returns the account object with that name."""
64
+ accounts = [vpc.account for vpc in data]
65
+
66
+ if account_name:
67
+ accounts = [account for account in accounts if account.name == account_name]
68
+
69
+ return accounts
70
+
71
+ def _handle_outputs(
72
+ self, requests: Iterable[VPCRequest], outputs: Mapping[str, Any]
73
+ ) -> Mapping[str, Any]:
74
+ """Receives a terraform outputs dict and returns a map of outputs per VPC requests"""
75
+ outputs_per_request: MutableMapping[str, Any] = {}
76
+ for request in requests:
77
+ outputs_per_request[request.identifier] = []
78
+ outputs_per_account = outputs[request.account.name]
79
+
80
+ # If the output exists for that request get its value
81
+ # Else get None
82
+ private_subnets = outputs_per_account.get(
83
+ f"{request.identifier}-private_subnets", {}
84
+ ).get("value", [])
85
+ public_subnets = outputs_per_account.get(
86
+ f"{request.identifier}-public_subnets", {}
87
+ ).get("value", [])
88
+
89
+ values = {
90
+ "static": {
91
+ "vpc_id": outputs_per_account.get(
92
+ f"{request.identifier}-vpc_id", {}
93
+ ).get("value"),
94
+ "subnets": {
95
+ "private": private_subnets,
96
+ "public": public_subnets,
97
+ },
98
+ "account_name": request.account.name,
99
+ "region": request.region,
100
+ "cidr_block": request.cidr_block.network_address,
101
+ "identifier": request.identifier,
102
+ }
103
+ }
104
+
105
+ outputs_per_request[request.identifier] = values
106
+
107
+ return outputs_per_request
108
+
109
+ def _render_template(self, template: str, data: Mapping[str, Any]) -> str:
110
+ return jinja2.Template(
111
+ template,
112
+ undefined=jinja2.StrictUndefined,
113
+ trim_blocks=False,
114
+ lstrip_blocks=False,
115
+ keep_trailing_newline=False,
116
+ ).render(data)
117
+
118
+ def run(self, dry_run: bool) -> None:
119
+ account_name = self.params.account_name
120
+ thread_pool_size = self.params.thread_pool_size
121
+ enable_deletion = self.params.enable_deletion
122
+
123
+ vault_settings = get_app_interface_vault_settings()
124
+ secret_reader = create_secret_reader(use_vault=vault_settings.vault)
125
+
126
+ gql_api = gql.get_api()
127
+ data = get_aws_vpc_requests(gql_api=gql_api)
128
+
129
+ if data:
130
+ accounts = self._filter_accounts(data, account_name)
131
+ if account_name and not accounts:
132
+ error_msg = f"The account {account_name} doesn't have any managed vpc. Verify your input"
133
+ logging.error(error_msg)
134
+ raise NoManagedVPCForAccount(error_msg)
135
+ else:
136
+ logging.warning("No VPC requests found, nothing to do.")
137
+ sys.exit(ExitCodes.SUCCESS)
138
+
139
+ accounts_untyped: list[dict] = [acc.dict(by_alias=True) for acc in accounts]
140
+ with TerrascriptClient(
141
+ integration=QONTRACT_INTEGRATION,
142
+ integration_prefix=QONTRACT_TF_PREFIX,
143
+ thread_pool_size=thread_pool_size,
144
+ accounts=accounts_untyped,
145
+ secret_reader=secret_reader,
146
+ ) as ts_client:
147
+ ts_client.populate_vpc_requests(data, AWS_PROVIDER_VERSION)
148
+
149
+ working_dirs = ts_client.dump(print_to_file=self.params.print_to_file)
150
+
151
+ if self.params.print_to_file:
152
+ sys.exit(ExitCodes.SUCCESS)
153
+
154
+ tf_client = TerraformClient(
155
+ integration=QONTRACT_INTEGRATION,
156
+ integration_version=QONTRACT_INTEGRATION_VERSION,
157
+ integration_prefix=QONTRACT_TF_PREFIX,
158
+ accounts=accounts_untyped,
159
+ working_dirs=working_dirs,
160
+ thread_pool_size=thread_pool_size,
161
+ )
162
+
163
+ tf_client.plan(enable_deletion=enable_deletion)
164
+
165
+ if dry_run:
166
+ sys.exit(ExitCodes.SUCCESS)
167
+
168
+ tf_client.apply()
169
+
170
+ handled_output = self._handle_outputs(data, tf_client.outputs)
171
+
172
+ # MR and template Management
173
+ vcs = VCS(
174
+ secret_reader=secret_reader,
175
+ github_orgs=get_github_orgs(),
176
+ gitlab_instances=get_gitlab_instances(),
177
+ app_interface_repo_url=get_app_interface_repo_url(),
178
+ dry_run=dry_run,
179
+ allow_deleting_mrs=False,
180
+ allow_opening_mrs=True,
181
+ )
182
+
183
+ mr_manager = MergeRequestManager(
184
+ vcs=vcs,
185
+ renderer=Renderer(),
186
+ parser=create_parser(),
187
+ auto_merge_enabled=True,
188
+ )
189
+
190
+ mr_manager._fetch_managed_open_merge_requests()
191
+
192
+ # Create a MR for each vpc request if the MR don't exist yet
193
+ for _, outputs in handled_output.items():
194
+ template = gql_api.get_template(
195
+ path="/templating/templates/terraform-vpc-resources/vpc.yml"
196
+ )["template"]
197
+ content = self._render_template(template=template, data=outputs)
198
+
199
+ mr_manager.create_merge_request(
200
+ MrData(
201
+ account=outputs["static"]["account_name"],
202
+ content=content,
203
+ path=f"data/aws/{outputs['static']['account_name']}/vpcs/{outputs['static']['identifier']}.yml",
204
+ )
205
+ )
206
+
207
+ def get_desired_state_shard_config(self) -> DesiredStateShardConfig:
208
+ return DesiredStateShardConfig(
209
+ shard_arg_name="account_name",
210
+ shard_path_selectors={
211
+ "accounts[*].name",
212
+ },
213
+ sharded_run_review=lambda proposal: len(proposal.proposed_shards) <= 2,
214
+ )
@@ -0,0 +1,57 @@
1
+ import re
2
+ import string
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from reconcile.utils.merge_request_manager.parser import Parser
7
+
8
+ PROMOTION_DATA_SEPARATOR = "**DO NOT MANUALLY CHANGE ANYTHING BELOW THIS LINE**"
9
+ VERSION = "0.1.0"
10
+ LABEL = "terraform-vpc-resources"
11
+
12
+ VERSION_REF = "tf_vpc_resources_version"
13
+ ACCOUNT_REF = "account"
14
+ COMPILED_REGEXES = {
15
+ i: re.compile(rf".*{i}: (.*)$", re.MULTILINE) for i in [VERSION_REF, ACCOUNT_REF]
16
+ }
17
+
18
+ DESC = string.Template(
19
+ f"""
20
+ This MR is triggered by app-interface's [terraform-vpc-resources](https://github.com/app-sre/qontract-reconcile/tree/master/reconcile/terraform_vpc_request).
21
+
22
+ Please **do not remove** the **{LABEL}** label from this MR!
23
+
24
+ Parts of this description are used by integration to manage the MR.
25
+
26
+ {PROMOTION_DATA_SEPARATOR}
27
+
28
+ * {VERSION_REF}: {VERSION}
29
+ * {ACCOUNT_REF}: $account
30
+ """
31
+ )
32
+
33
+
34
+ class Info(BaseModel):
35
+ account: str
36
+
37
+
38
+ def create_parser() -> Parser:
39
+ """Create a parser for MRs created by terraform-vpc-resources."""
40
+
41
+ return Parser[Info](
42
+ klass=Info,
43
+ compiled_regexes=COMPILED_REGEXES,
44
+ version_ref=VERSION_REF,
45
+ expected_version=VERSION,
46
+ data_separator=PROMOTION_DATA_SEPARATOR,
47
+ )
48
+
49
+
50
+ class Renderer:
51
+ """This class is only concerned with rendering text for MRs."""
52
+
53
+ def render_description(self, account: str) -> str:
54
+ return DESC.safe_substitute(account=account)
55
+
56
+ def render_title(self, account: str) -> str:
57
+ return f"[auto] VPC data file creation to {account}"
@@ -0,0 +1,107 @@
1
+ import logging
2
+
3
+ from gitlab.exceptions import GitlabGetError
4
+ from pydantic import BaseModel
5
+
6
+ from reconcile.terraform_vpc_resources.merge_request import (
7
+ LABEL,
8
+ Info,
9
+ Renderer,
10
+ )
11
+ from reconcile.utils.gitlab_api import GitLabApi
12
+ from reconcile.utils.merge_request_manager.merge_request_manager import (
13
+ MergeRequestManagerBase,
14
+ )
15
+ from reconcile.utils.merge_request_manager.parser import Parser
16
+ from reconcile.utils.mr.base import MergeRequestBase
17
+ from reconcile.utils.mr.labels import AUTO_MERGE
18
+ from reconcile.utils.vcs import VCS
19
+
20
+
21
+ class VPCRequestMR(MergeRequestBase):
22
+ name = "VPCRequest"
23
+
24
+ def __init__(
25
+ self,
26
+ title: str,
27
+ description: str,
28
+ vpc_tmpl_file_path: str,
29
+ vpc_tmpl_file_content: str,
30
+ labels: list[str],
31
+ ):
32
+ super().__init__()
33
+ self._title = title
34
+ self._description = description
35
+ self._vpc_tmpl_file_path = vpc_tmpl_file_path
36
+ self._vpc_tmpl_file_content = vpc_tmpl_file_content
37
+ self.labels = labels
38
+
39
+ @property
40
+ def title(self) -> str:
41
+ return self._title
42
+
43
+ @property
44
+ def description(self) -> str:
45
+ return self._description
46
+
47
+ def process(self, gitlab_cli: GitLabApi) -> None:
48
+ gitlab_cli.create_file(
49
+ branch_name=self.branch,
50
+ file_path=self._vpc_tmpl_file_path,
51
+ commit_message="add vpc datafile",
52
+ content=self._vpc_tmpl_file_content,
53
+ )
54
+
55
+
56
+ class MrData(BaseModel):
57
+ account: str
58
+ content: str
59
+ path: str
60
+
61
+
62
+ class MergeRequestManager(MergeRequestManagerBase[Info]):
63
+ """Manager for the merge requests.
64
+
65
+ This class is responsible for housekeeping (closing old/bad MRs) and
66
+ opening new MRs.
67
+ """
68
+
69
+ def __init__(
70
+ self, vcs: VCS, renderer: Renderer, parser: Parser, auto_merge_enabled: bool
71
+ ):
72
+ super().__init__(vcs, parser, LABEL)
73
+ self._renderer = renderer
74
+ self._auto_merge_enabled = auto_merge_enabled
75
+
76
+ def create_merge_request(self, data: MrData) -> None:
77
+ """Open a new MR, if not already present, for a VPC datafile and close any outdated before."""
78
+ if not self._housekeeping_ran:
79
+ self.housekeeping()
80
+
81
+ if self._merge_request_already_exists({"account": data.account}):
82
+ logging.info("MR already exists for %s", data.account)
83
+ return None
84
+
85
+ try:
86
+ self._vcs.get_file_content_from_app_interface_master(file_path=data.path)
87
+ # the file exists, nothing to do
88
+ return None
89
+ except GitlabGetError as e:
90
+ if e.response_code != 404:
91
+ raise
92
+
93
+ description = self._renderer.render_description(account=data.account)
94
+ title = self._renderer.render_title(account=data.account)
95
+ logging.info("Open MR for %s", data.account)
96
+ mr_labels = [LABEL]
97
+ if self._auto_merge_enabled:
98
+ mr_labels.append(AUTO_MERGE)
99
+ self._vcs.open_app_interface_merge_request(
100
+ mr=VPCRequestMR(
101
+ vpc_tmpl_file_path=data.path,
102
+ title=title,
103
+ description=description,
104
+ vpc_tmpl_file_content=data.content,
105
+ labels=mr_labels,
106
+ )
107
+ )
@@ -0,0 +1,11 @@
1
+ from typing import Optional
2
+
3
+ from reconcile.gql_definitions.common.aws_vpc_requests import VPCRequest, query
4
+ from reconcile.utils import gql
5
+ from reconcile.utils.gql import GqlApi
6
+
7
+
8
+ def get_aws_vpc_requests(gql_api: Optional[GqlApi] = None) -> list[VPCRequest]:
9
+ api = gql_api if gql_api else gql.get_api()
10
+ data = query(query_func=api.query)
11
+ return list(data.vpc_requests or [])
reconcile/utils/gql.py CHANGED
@@ -178,6 +178,29 @@ class GqlApi:
178
178
 
179
179
  return result["data"]
180
180
 
181
+ def get_template(self, path: str) -> dict[str, str]:
182
+ query = """
183
+ query Template($path: String) {
184
+ templates: template_v1(path: $path) {
185
+ path
186
+ template
187
+ }
188
+ }
189
+ """
190
+
191
+ try:
192
+ templates = []
193
+ q_result = self.query(query, {"path": path})
194
+ if q_result:
195
+ templates = q_result["templates"]
196
+ except GqlApiError:
197
+ raise GqlGetResourceError(path, "Template not found.")
198
+
199
+ if len(templates) != 1:
200
+ raise GqlGetResourceError(path, "Expecting one and only one template.")
201
+
202
+ return templates[0]
203
+
181
204
  def get_resource(self, path: str) -> dict[str, Any]:
182
205
  query = """
183
206
  query Resource($path: String) {
@@ -36,6 +36,7 @@ from sretoolbox.utils import threaded
36
36
  from terrascript import (
37
37
  Backend,
38
38
  Data,
39
+ Module,
39
40
  Output,
40
41
  Provider,
41
42
  Resource,
@@ -144,6 +145,9 @@ import reconcile.utils.aws_helper as awsh
144
145
  from reconcile import queries
145
146
  from reconcile.cli import TERRAFORM_VERSION
146
147
  from reconcile.github_org import get_default_config
148
+ from reconcile.gql_definitions.fragments.aws_vpc_request import (
149
+ VPCRequest,
150
+ )
147
151
  from reconcile.gql_definitions.terraform_resources.terraform_resources_namespaces import (
148
152
  NamespaceTerraformResourceLifecycleV1,
149
153
  )
@@ -1181,6 +1185,66 @@ class TerrascriptClient: # pylint: disable=too-many-public-methods
1181
1185
  tf_resource = aws_route(route_identifier, **values)
1182
1186
  self.add_resource(infra_account_name, tf_resource)
1183
1187
 
1188
+ def populate_vpc_requests(
1189
+ self, vpc_requests: Iterable[VPCRequest], aws_provider_version: str
1190
+ ) -> None:
1191
+ for request in vpc_requests:
1192
+ # The default values here come from infra repo's module configuration
1193
+ values = {
1194
+ "source": "terraform-aws-modules/vpc/aws",
1195
+ "version": aws_provider_version,
1196
+ "name": request.identifier,
1197
+ "cidr": request.cidr_block.network_address,
1198
+ "private_subnet_tags": {"kubernetes.io/role/internal-elb": "1"},
1199
+ "public_subnet_tags": {"kubernetes.io/role/elb": "1"},
1200
+ "create_database_subnet_group": False,
1201
+ "enable_dns_hostnames": True,
1202
+ "tags": {
1203
+ "managed_by_integration": self.integration,
1204
+ },
1205
+ }
1206
+
1207
+ if request.subnets and request.subnets.public:
1208
+ values["public_subnets"] = request.subnets.public
1209
+ if request.subnets and request.subnets.private:
1210
+ values["private_subnets"] = request.subnets.private
1211
+ if request.subnets and request.subnets.availability_zones:
1212
+ values["azs"] = request.subnets.availability_zones
1213
+
1214
+ # We only want to enable nat_gateway if we have public and private subnets
1215
+ if request.subnets and request.subnets.public and request.subnets.private:
1216
+ values["enable_nat_gateway"] = True
1217
+
1218
+ aws_account = request.account.name
1219
+ module = Module(request.identifier, **values)
1220
+ self.add_resource(aws_account, module)
1221
+
1222
+ # The outputs for module are only working with this sintaxe
1223
+ vpc_id_output = Output(
1224
+ f"{request.identifier}-vpc_id", value=f"${{{module.vpc_id}}}"
1225
+ )
1226
+ self.add_resource(aws_account, vpc_id_output)
1227
+
1228
+ vpc_cidr_block_output = Output(
1229
+ f"{request.identifier}-vpc_cidr_block",
1230
+ value=f"${{{module.vpc_cidr_block}}}",
1231
+ )
1232
+ self.add_resource(aws_account, vpc_cidr_block_output)
1233
+
1234
+ if request.subnets and request.subnets.private:
1235
+ private_subnets_output = Output(
1236
+ f"{request.identifier}-private_subnets",
1237
+ value=f"${{module.{request.identifier}.private_subnets}}",
1238
+ )
1239
+ self.add_resource(aws_account, private_subnets_output)
1240
+
1241
+ if request.subnets and request.subnets.public:
1242
+ public_subnets_output = Output(
1243
+ f"{request.identifier}-public_subnets",
1244
+ value=f"${{module.{request.identifier}.public_subnets}}",
1245
+ )
1246
+ self.add_resource(aws_account, public_subnets_output)
1247
+
1184
1248
  def populate_tgw_attachments(self, desired_state):
1185
1249
  for item in desired_state:
1186
1250
  if item.deleted: