1212# See the License for the specific language governing permissions and 
1313# limitations under the License. 
1414
15- import  json 
15+ import  json ,  yaml 
1616
1717from  ansible .module_utils .common .text .converters  import  to_text , to_native 
1818
5555    ServicesResourceApi ,
5656    RoleConfigGroupsResourceApi ,
5757    RolesResourceApi ,
58+     ControlPlanesResourceApi ,
59+     ApiInstallEmbeddedControlPlaneArgs ,
5860)
5961from  cm_client .rest  import  ApiException 
6062
348350            required: no 
349351            aliases: 
350352              - role_type 
353+   control_plane: 
354+     description: 
355+       - Private Cloud Control Plane on embedded kubernetes 
356+     type: dict 
357+     required: no 
358+     suboptions: 
359+       remote_repo_url: 
360+         description: 
361+           - The url of the remote repository where the private cloud artifacts to install are hosted. 
362+         type: str 
363+         required: yes 
364+       datalake_cluster_name: 
365+         description: 
366+           - The name of the datalake cluster to use for the initial environment in this control plane. 
367+         type: str 
368+         required: yes 
369+       control_plane_config: 
370+         description: 
371+           - A yaml structured dictionary with configuration parameters for the installation. 
372+         type: dict 
373+         required: yes 
374+         aliases: 
375+           - values_yaml 
351376  parcels: 
352377    description: 
353378      - The parcels by version enabled for a cluster. 
629654    username: "jane_smith" 
630655    password: "S&peR4Ec*re" 
631656    port: 7180 
632-     cluster_name: example -cluster 
657+     cluster_name: ECS -cluster 
633658    cluster_version: "1.5.1-b626.p0.42068229" 
634659    cluster_type: EXPERIENCE_CLUSTER 
635660    state: present 
661+     parcels: 
662+       ECS: "1.5.1-b626.p0.42068229" 
663+     services: 
664+       - name: docker 
665+         type: DOCKER 
666+         config: 
667+           docker_images_destination_registry_user: registry-user 
668+           defaultDataPath: /mnt/docker 
669+       - name: ecs 
670+         type: ECS 
671+         config: 
672+           app_domain: test.lab.example 
673+           k8s_webui_secret_admin_token: ecs-k8s_webui_secret_admin_token 
674+           cp_prometheus_ingress_user: cloudera-manager 
675+           infra_prometheus_ingress_user: cloudera-manager 
676+           longhorn_replication: 2 
677+           lsoDataPath: /ecs/local 
678+           docker: docker 
679+           cp_prometheus_ingress_password: password1 
680+           infra_prometheus_ingress_password: password1 
681+           defaultDataPath: /ecs/longhorn-storage 
682+           nfs_over_provisioning: 800 
683+     host_templates: 
684+       - name: ecs_master 
685+         role_groups: 
686+           - service_type: DOCKER 
687+             type: DOCKER_SERVER 
688+           - service_type: ECS 
689+             type: ECS_SERVER 
690+       - name: ecs_workers 
691+         role_groups: 
692+           - service_type: DOCKER 
693+             type: DOCKER_SERVER 
694+           - service_type: ECS 
695+             type: ECS_AGENT 
696+     hosts: 
697+       - name: ecs-master-01.test.lab.example 
698+         host_template: ecs_master 
699+       - name: ecs-worker-01.test.lab.example 
700+         host_template: ecs_workers 
701+       - name: ecs-worker-02.test.lab.example 
702+         host_template: ecs_workers 
703+       - name: ecs-worker-03.test.lab.example 
704+         host_template: ecs_workers 
705+     control_plane: 
706+       datalake_cluster_name: PVC-Base 
707+       remote_repo_url: "https://test_website/cdp-pvc-ds/1.5.1" 
708+       control_plane_config: 
709+         ContainerInfo: 
710+                Mode: public 
711+                CopyDocker: false 
712+         Database: 
713+           Mode: embedded 
714+           EmbeddedDbStorage: 50 
715+         Services: 
716+           thunderheadenvironment: 
717+             Config: 
718+               database: 
719+                 name: db-env 
720+           mlxcontrolplaneapp: 
721+             Config: 
722+               database: 
723+                 name: db-mlx 
724+           dwx: 
725+             Config: 
726+               database: 
727+                 name: db-dwx 
728+           cpxliftie: 
729+             Config: 
730+               database: 
731+                 name: db-liftie 
732+           dex: 
733+             Config: 
734+               database: 
735+                 name: db-dex 
736+           resourcepoolmanager: 
737+             Config: 
738+               database: 
739+                 name: db-resourcepoolmanager 
740+           cdpcadence: 
741+             Config: 
742+               database: 
743+                 name: db-cadence 
744+           cdpcadencevisibility: 
745+             Config: 
746+               database: 
747+                 name: db-cadence-visibility 
748+           clusteraccessmanager: 
749+             Config: 
750+               database: 
751+                 name: db-clusteraccessmanager 
752+           monitoringapp: 
753+             Config: 
754+               database: 
755+                 name: db-alerts 
756+           thunderheadusermanagementprivate: 
757+             Config: 
758+               database: 
759+                 name: db-ums 
760+           classicclusters: 
761+             Config: 
762+               database: 
763+                 name: cm-registration 
764+           clusterproxy: 
765+             Config: 
766+               database: 
767+                 name: cluster-proxy 
768+           dssapp: 
769+             Config: 
770+               database: 
771+                 name: db-dss-app 
772+         Vault: 
773+           Mode: embedded 
636774""" 
637775
638776RETURN  =  r""" 
@@ -698,6 +836,7 @@ def __init__(self, module):
698836        self .display_name  =  self .get_param ("display_name" )
699837        self .contexts  =  self .get_param ("contexts" )
700838        self .auto_assign  =  self .get_param ("auto_assign" )
839+         self .control_plane  =  self .get_param ("control_plane" )
701840
702841        self .changed  =  False 
703842        self .output  =  {}
@@ -717,6 +856,7 @@ def process(self):
717856        self .host_api  =  HostsResourceApi (self .api_client )
718857        self .role_group_api  =  RoleConfigGroupsResourceApi (self .api_client )
719858        self .role_api  =  RolesResourceApi (self .api_client )
859+         self .control_plane_api  =  ControlPlanesResourceApi (self .api_client )
720860
721861        refresh  =  True 
722862
@@ -991,15 +1131,16 @@ def create_cluster_from_parameters(self):
9911131                                rcg ["name" ]
9921132                                if  rcg ["name" ]
9931133                                else  self .find_base_role_group_name (
994-                                     service_type = rcg ["service" ], role_type = rcg ["type" ]
1134+                                     service_name = rcg ["service" ],
1135+                                     service_type = rcg ["service_type" ],
1136+                                     role_type = rcg ["type" ],
9951137                                )
9961138                            )
9971139                            for  rcg  in  ht ["role_groups" ]
9981140                        ],
9991141                    )
10001142                    for  ht  in  self .host_templates 
10011143                ]
1002- 
10031144                self .host_template_api .create_host_templates (
10041145                    cluster_name = self .name ,
10051146                    body = ApiHostTemplateList (items = templates ),
@@ -1174,6 +1315,25 @@ def create_cluster_from_parameters(self):
11741315                            msg = "Role not found. No role type '%s' for service '%s' found on host '%s'" 
11751316                            %  (override ["type" ], override ["service" ], hostref .hostname )
11761317                        )
1318+             # Configure the experience cluster 
1319+             if  self .control_plane :
1320+                 values_yaml_data  =  self .control_plane ["control_plane_config" ]
1321+                 values_yaml_str  =  yaml .dump (values_yaml_data )
1322+ 
1323+                 # Assemble body for Install Control Plane request 
1324+                 body  =  ApiInstallEmbeddedControlPlaneArgs (
1325+                     experience_cluster_name = self .name ,
1326+                     containerized_cluster_name = self .name ,
1327+                     datalake_cluster_name = self .control_plane ["datalake_cluster_name" ],
1328+                     remote_repo_url = self .control_plane ["remote_repo_url" ],
1329+                     values_yaml = values_yaml_str ,
1330+                 )
1331+                 setup_control_plane  =  (
1332+                     self .control_plane_api .install_embedded_control_plane (body = body )
1333+                 )
1334+                 self .wait_for_command_state (
1335+                     command_id = setup_control_plane .id , polling_interval = self .delay 
1336+                 )
11771337
11781338            # Execute auto-role assignments 
11791339            if  self .auto_assign :
@@ -1246,15 +1406,28 @@ def marshal_hostrefs(self, hosts: dict) -> list[ApiHostRef]:
12461406            )
12471407        return  results 
12481408
1249-     def  find_base_role_group_name (self , service_type : str , role_type : str ) ->  str :
1250-         rcgs  =  [
1251-             rcg 
1252-             for  s  in  self .service_api .read_services (cluster_name = self .name ).items 
1253-             for  rcg  in  self .role_group_api .read_role_config_groups (
1254-                 cluster_name = self .name , service_name = s .name 
1255-             ).items 
1256-             if  s .type  ==  service_type 
1257-         ]
1409+     def  find_base_role_group_name (
1410+         self , role_type : str , service_name : str  =  None , service_type : str  =  None 
1411+     ) ->  str :
1412+         if  service_name :
1413+ 
1414+             rcgs  =  [
1415+                 rcg 
1416+                 for  s  in  self .service_api .read_services (cluster_name = self .name ).items 
1417+                 for  rcg  in  self .role_group_api .read_role_config_groups (
1418+                     cluster_name = self .name , service_name = s .name 
1419+                 ).items 
1420+                 if  s .name  ==  service_name 
1421+             ]
1422+         elif  service_type :
1423+             rcgs  =  [
1424+                 rcg 
1425+                 for  s  in  self .service_api .read_services (cluster_name = self .name ).items 
1426+                 for  rcg  in  self .role_group_api .read_role_config_groups (
1427+                     cluster_name = self .name , service_name = s .name 
1428+                 ).items 
1429+                 if  s .type  ==  service_type 
1430+             ]
12581431
12591432        base  =  next (
12601433            iter ([rcg  for  rcg  in  rcgs  if  rcg .base  and  rcg .role_type  ==  role_type ]),
@@ -1263,8 +1436,8 @@ def find_base_role_group_name(self, service_type: str, role_type: str) -> str:
12631436
12641437        if  base  is  None :
12651438            self .module .fail_json (
1266-                 "Invalid role group; unable to discover base role group for service role, %s[%s] " 
1267-                 %  ( role_type ,  service_type ) 
1439+                 "Invalid role group; unable to discover base role group for service role, %s" 
1440+                 %  role_type 
12681441            )
12691442        else :
12701443            return  base .name 
@@ -1367,21 +1540,30 @@ def main():
13671540                        required = True ,
13681541                        options = dict (
13691542                            name = dict (aliases = ["ref" , "ref_name" ]),
1370-                             service = dict (
1371-                                 required = True , aliases = ["service_name" , "service_ref" ]
1372-                             ),
1543+                             service = dict (aliases = ["service_name" , "service_ref" ]),
13731544                            type = dict (aliases = ["role_type" ]),
1545+                             service_type = dict (),
13741546                        ),
13751547                        aliases = ["role_config_groups" ],
13761548                        mutually_exclusive = [
1377-                             ("name " , "type " ),
1549+                             ("service " , "service_type " ),
13781550                        ],
13791551                        requires_one_of = [
1380-                             ("name " , "type " ),
1552+                             ("service " , "service_type " ),
13811553                        ],
13821554                    ),
13831555                ),
13841556            ),
1557+             control_plane = dict (
1558+                 type = "dict" ,
1559+                 options = dict (
1560+                     remote_repo_url = dict (required = True , type = "str" ),
1561+                     datalake_cluster_name = dict (required = True , type = "str" ),
1562+                     control_plane_config = dict (
1563+                         required = True , type = "dict" , aliases = ["values_yaml" ]
1564+                     ),
1565+                 ),
1566+             ),
13851567            # Parcels is a dict of product:version of the cluster 
13861568            parcels = dict (type = "dict" , aliases = ["products" ]),
13871569            # Tags is a dict of key:value assigned to the cluster 
0 commit comments