From 11a8f4dc2b314d6885b4e50e1dd0c298f4c43cac Mon Sep 17 00:00:00 2001 From: David Danyi Date: Thu, 15 Oct 2020 13:02:54 +0200 Subject: [PATCH] * working version --- automatique.env.yaml.example | 272 ++++++++++++++ automatique.task.yaml.example | 34 ++ composer.json | 2 + composer.lock | 332 +++++++++++++++++- config/config.php | 1 + src/App/Command/AutoOptions.php | 37 ++ src/App/Command/BuildPackage.php | 57 +-- src/App/Command/Cleanup.php | 52 +++ src/App/Command/CleanupFactory.php | 21 ++ src/App/Command/ComplexTaskConfig.php | 61 +++- src/App/Command/DownloadArmImage.php | 44 +++ src/App/Command/DownloadArmImageFactory.php | 18 + src/App/Command/EnsureImagePresent.php | 55 +++ src/App/Command/EnsureImagePresentFactory.php | 21 ++ src/App/Command/GenerateOnboardPackage.php | 37 +- src/App/Command/ListNodeImages.php | 51 +++ src/App/Command/ListNodeImagesFactory.php | 18 + src/App/Command/OnboardPackage.php | 52 +++ src/App/Command/OnboardPackageFactory.php | 20 ++ src/App/Command/PrepareTemplates.php | 53 +++ src/App/Command/PrepareTemplatesFactory.php | 21 ++ src/App/Command/SharedStorage.php | 2 +- src/App/Command/UploadImage.php | 53 +++ src/App/Command/UploadImageFactory.php | 21 ++ src/App/ConfigProvider.php | 18 +- src/App/Downloader/SCP.php | 2 +- src/App/Runner/RunnerInterface.php | 2 +- src/App/Runner/SSH.php | 50 ++- src/App/Service/ArmImageDownloader.php | 73 ++++ src/App/Service/ArmImageDownloaderFactory.php | 19 + src/App/Service/AtlasManager.php | 120 +++++++ src/App/Service/AtlasManagerFactory.php | 18 + src/App/Service/Builder.php | 111 +++++- src/App/Service/BuilderFactory.php | 7 +- src/App/Service/Cleanup.php | 33 ++ src/App/Service/CleanupFactory.php | 17 + src/App/Service/ConfigProvider.php | 6 +- src/App/Service/TemplateGenerator.php | 160 +++++++++ src/App/Service/TemplateGeneratorFactory.php | 19 + 39 files changed, 1904 insertions(+), 86 deletions(-) create mode 100644 automatique.env.yaml.example create mode 100644 automatique.task.yaml.example create mode 100644 src/App/Command/AutoOptions.php create mode 100644 src/App/Command/Cleanup.php create mode 100644 src/App/Command/CleanupFactory.php create mode 100644 src/App/Command/DownloadArmImage.php create mode 100644 src/App/Command/DownloadArmImageFactory.php create mode 100644 src/App/Command/EnsureImagePresent.php create mode 100644 src/App/Command/EnsureImagePresentFactory.php create mode 100644 src/App/Command/ListNodeImages.php create mode 100644 src/App/Command/ListNodeImagesFactory.php create mode 100644 src/App/Command/OnboardPackage.php create mode 100644 src/App/Command/OnboardPackageFactory.php create mode 100644 src/App/Command/PrepareTemplates.php create mode 100644 src/App/Command/PrepareTemplatesFactory.php create mode 100644 src/App/Command/UploadImage.php create mode 100644 src/App/Command/UploadImageFactory.php create mode 100644 src/App/Service/ArmImageDownloader.php create mode 100644 src/App/Service/ArmImageDownloaderFactory.php create mode 100644 src/App/Service/AtlasManager.php create mode 100644 src/App/Service/AtlasManagerFactory.php create mode 100644 src/App/Service/Cleanup.php create mode 100644 src/App/Service/CleanupFactory.php create mode 100644 src/App/Service/TemplateGenerator.php create mode 100644 src/App/Service/TemplateGeneratorFactory.php diff --git a/automatique.env.yaml.example b/automatique.env.yaml.example new file mode 100644 index 0000000..b8ea706 --- /dev/null +++ b/automatique.env.yaml.example @@ -0,0 +1,272 @@ +vnf-lcm: + mtas-41-vnflcm: &mtas-41-vnflcm + ssh-alias: mtas-41-vnflcm + socks-proxy: localhost:1081 + storage: cinder + address: 10.80.84.14 + user: cloud-user + mtas-55-vnflcm: &mtas-55-vnflcm + ssh-alias: mtas-55-vnflcm + socks-proxy: localhost:1081 + storage: ephemeral + address: 10.80.99.142 + user: cloud-user + +vim: + mtas-41-atlas: &mtas-41-atlas + ssh-alias: mtas-41-atlas + type: openstack + storage: cinder + address: 10.80.84.20 + user: atlasadm + mtas-55-atlas: &mtas-55-atlas + ssh-alias: mtas-55-atlas + type: openstack + storage: ephemeral + address: 10.80.99.148 + user: atlasadm + +node: + mtas-41-1: + vim: *mtas-41-atlas + vnf-lcm: *mtas-41-vnflcm + project: MTAS-41-1 + openrc: openrc-41-1 + template-params: + legacy: + -p: "profile1" + -s: "cinder" +# -v: "ipv4" + -v: "dualstack" + -n: "VLAN" + -b: "True" + jinja: + internal_network: one_int_net + vip_fe_network_number: 3 +# ip_version: ipv4 + ip_version: ipv4ipv6 + local_disk_realization: cinder-volume + ha_policy: none + network_type: VLAN + maximum_number_of_PLs: 10 + port_security_vip_external: disable + port_security_non_vip_external: disable + wait_handler: disable + env: + shared: + availability_zone: {'*':mtas} + VM_HA_policy_SC: managed-on-host + VM_HA_policy_PL: ha-offline + VM_image_PL: CXP9028685_1-R3A + SC-1_SC-2_cinder_volume_size: 50 + number_of_scaled_out_VMs: 0 + VM_to_be_scaled_in: [] + nw_internal_mtu: 1500 + OM_IPv4_address: 10.80.103.12 + SC-1_IPv4_address: 10.80.103.13 + SC-2_IPv4_address: 10.80.103.14 + OM_IPv6_address: 2001:1b70:8294:3d61::4 + SC-1_IPv6_address: 2001:1b70:8294:3d61::5 + SC-2_IPv6_address: 2001:1b70:8294:3d61::6 + DNS_server_1_address: 10.51.40.100 + DNS_server_2_address: 10.51.40.101 + DNS_server_3_address: 10.64.73.100 + DNS_server_4_address: 10.64.73.101 + NTP_server_1_address: 10.51.40.102 + NTP_server_2_address: 10.51.40.103 + NTP_server_3_address: 10.64.73.102 + NTP_server_4_address: 10.64.73.103 + defaultURI: none + defaultSecURI: none + time_zone: Europe/Stockholm + watchdog_timeout: 30 + interval_timeout: 10 + shutdown_timeout: 600 + platform-vip: 10.80.103.128 + platform-vip6: 2001:1b70:8294:3d00::4110 + tasvip4: 10.80.103.129 + tasvip6: 2001:1b70:8294:3d00::4111 + ut-vip4: 10.80.103.130 + ut-vip6: 2001:1b70:8294:3d00::4112 + cai3g-vip4: 10.80.103.131 + cai3g-vip6: 2001:1b70:8294:3d00::4113 + sigtran1-vip4: 10.80.103.132 + sigtran1-vip6: 2001:1b70:8294:3d00::4114 + sigtran2-vip4: 10.80.103.133 + sigtran2-vip6: 2001:1b70:8294:3d00::4115 + emergency_username: maintenance + emergency_password_hash: $6$Kjfq3r8jjaVG$A9/prKfLo9a4TKTaGSyDdk0rxk24IuoGr1SXmj.LTu81Ev9eZ8FBDjow7TEgNb.4t2i8w2LDF/R/VuIKknpLk0 + secla_password_hash: $6$Kjfq3r8jjaVG$A9/prKfLo9a4TKTaGSyDdk0rxk24IuoGr1SXmj.LTu81Ev9eZ8FBDjow7TEgNb.4t2i8w2LDF/R/VuIKknpLk0 + root_password_hash: $6$Kjfq3r8jjaVG$A9/prKfLo9a4TKTaGSyDdk0rxk24IuoGr1SXmj.LTu81Ev9eZ8FBDjow7TEgNb.4t2i8w2LDF/R/VuIKknpLk0 + oss_pm_password_hash: $6$Kjfq3r8jjaVG$A9/prKfLo9a4TKTaGSyDdk0rxk24IuoGr1SXmj.LTu81Ev9eZ8FBDjow7TEgNb.4t2i8w2LDF/R/VuIKknpLk0 + DbnVMSharedMemoryPercentage: 37 + DbnVMRecordHeapPercentage: 80 + MultiMMapMaxMem: 32 + jvm_initial_heap_size_Xms: 768m + jvm_initial_heap_size_eden_Xmn: 512m + jvm_max_heap_size_Xmx: 1536m + internal_net_mtu: 1500 + mtu_cache_refresh_timer: 600 + legacy: + # VM_flavor: vMTAS_16-54 + VM_flavor: vMTAS_4-27 + VM_image_SC: MTASv_4_CXP9031366-R19E05 + VM_image_SC_1: MTASv_4_CXP9031366-R19E05 + VM_image_SC_2: MTASv_4_CXP9031366-R19E05 + om_sp1_net_name: mtas-41-1-nw_om_sp1 + om_sp1_net_mtu: 1500 + om_sp1_subnet_IPv4_name: mtas-41-1-nw_om_sp1_subnet_IPv4 + om_sp1_subnet_IPv4_address: 10.80.103.8/29 + om_sp1_subnet_IPv4_gateway_address: 10.80.103.9 + om_sp1_subnet_IPv6_name: mtas-41-1-nw_om_sp1_subnet_IPv6 + om_sp1_subnet_IPv6_address: 2001:1b70:8294:3d61::/64 + om_sp1_subnet_IPv6_gateway_address: fe80::10:80:103:9 + om_sp2_net_name: mtas-41-1-nw_om_sp2 + om_sp2_net_mtu: 1500 + sig_sp_net_name: mtas-41-1-nw_sig_sp + sig_sp_net_mtu: 1500 + bar_sp_net_name: mtas-41-1-nw_bar_sp + bar_sp_net_mtu: 1500 + jinja: + # VM_flavor_SC: vMTAS_16-54 + # VM_flavor_PL: vMTAS_16-54 + VM_flavor_SC: vMTAS_4-27 + VM_flavor_PL: vMTAS_4-27 + VM_image_SC: MTASv_4_CXP9031366-R19E05 + VM_image_SC-1: MTASv_4_CXP9031366-R19E05 + VM_image_SC-2: MTASv_4_CXP9031366-R19E05 + PL_scheduling_policy: anti-affinity + om_net_name: mtas-41-1-nw_om_sp1 + om_net_mtu: 1500 + om_subnet_IPv4_name: mtas-41-1-nw_om_sp1_subnet_IPv4 + om_subnet_IPv4_address: 10.80.103.8/29 + om_subnet_IPv4_gateway_address: 10.80.103.9 + om_subnet_IPv6_name: mtas-41-1-nw_om_sp1_subnet_IPv6 + om_subnet_IPv6_address: 2001:1b70:8294:3d61::/64 + om_subnet_IPv6_gateway_address: fe80::10:80:103:9 + vipfrontend1_net_name: mtas-41-1-nw_om_sp2 + vipfrontend1_net_mtu: 1500 + vipfrontend2_net_name: mtas-41-1-nw_sig_sp + vipfrontend2_net_mtu: 1500 + vipfrontend3_net_name: mtas-41-1-nw_bar_sp + vipfrontend3_net_mtu: 1500 + + mtas-55-10: + vim: *mtas-55-atlas + vnf-lcm: *mtas-55-vnflcm + project: MTAS-55-10 + openrc: openrc-55-10 + template-params: + legacy: + -p: "profile1" + -s: "ephemeral" + -v: "dualstack" + -n: "VLAN" + -b: "True" + jinja: + internal_network: one_int_net + vip_fe_network_number: 3 + ip_version: ipv4ipv6 + local_disk_realization: nova-ephemeral + ha_policy: none + network_type: VLAN + maximum_number_of_PLs: 10 + port_security_vip_external: disable + port_security_non_vip_external: disable + env: + shared: + availability_zone: {'*':mtas} + VM_HA_policy_SC: managed-on-host + VM_HA_policy_PL: ha-offline + VM_image_PL: CXP9028685_1-R3A + SC-1_SC-2_cinder_volume_size: 50 + number_of_scaled_out_VMs: 0 + VM_to_be_scaled_in: [] + nw_internal_mtu: 1500 + OM_IPv4_address: 10.80.99.84 + SC-1_IPv4_address: 10.80.99.85 + SC-2_IPv4_address: 10.80.99.86 + OM_IPv6_address: 2001:1b70:8294:3e1a::4 + SC-1_IPv6_address: 2001:1b70:8294:3e1a::5 + SC-2_IPv6_address: 2001:1b70:8294:3e1a::6 + DNS_server_1_address: 10.51.40.100 + DNS_server_2_address: 10.51.40.101 + DNS_server_3_address: 10.64.73.100 + DNS_server_4_address: 10.64.73.101 + NTP_server_1_address: 10.51.40.102 + NTP_server_2_address: 10.51.40.103 + NTP_server_3_address: 10.64.73.102 + NTP_server_4_address: 10.64.73.103 + defaultURI: none + defaultSecURI: none + time_zone: Europe/Stockholm + watchdog_timeout: 30 + interval_timeout: 10 + shutdown_timeout: 600 + platform-vip: 10.80.99.232 + platform-vip6: 2001:1b70:8294:3e00::55a0 + tasvip4: 10.80.99.233 + tasvip6: 2001:1b70:8294:3e00::55a1 + ut-vip4: 10.80.99.234 + ut-vip6: 2001:1b70:8294:3e00::55a2 + cai3g-vip4: 10.80.99.235 + cai3g-vip6: 2001:1b70:8294:3e00::55a3 + sigtran1-vip4: 10.80.99.236 + sigtran1-vip6: 2001:1b70:8294:3e00::55a4 + sigtran2-vip4: 10.80.99.237 + sigtran2-vip6: 2001:1b70:8294:3e00::55a5 + emergency_password_hash: + secla_password_hash: + root_password_hash: + oss_pm_password_hash: + DbnVMSharedMemoryPercentage: 37 + DbnVMRecordHeapPercentage: 80 + MultiMMapMaxMem: 32 + jvm_initial_heap_size_Xms: 768m + jvm_initial_heap_size_eden_Xmn: 512m + jvm_max_heap_size_Xmx: 1536m + internal_net_mtu: 1500 + mtu_cache_refresh_timer: 600 + legacy: + VM_flavor_SC: vMTAS_4-27-200 + VM_flavor_PL: vMTAS_4-27 + VM_image_SC: MTASv_4_CXP9031366-R20C08 + VM_image_SC_1: MTASv_4_CXP9031366-R20C08 + VM_image_SC_2: MTASv_4_CXP9031366-R20C08 + om_sp1_net_name: mtas-55-10-nw_om_sp1 + om_sp1_net_mtu: 1500 + om_sp1_subnet_IPv4_name: mtas-55-10-nw_om_sp1_subnet_IPv4 + om_sp1_subnet_IPv4_address: 10.80.99.80/29 + om_sp1_subnet_IPv4_gateway_address: 10.80.99.81 + om_sp1_subnet_IPv6_name: mtas-55-10-nw_om_sp1_subnet_IPv6 + om_sp1_subnet_IPv6_address: 2001:1b70:8294:3e1a::/64 + om_sp1_subnet_IPv6_gateway_address: fe80::10:80:99:81 + om_sp2_net_name: mtas-55-10-nw_om_sp2 + om_sp2_net_mtu: 1500 + sig_sp_net_name: mtas-55-10-nw_sig_sp + sig_sp_net_mtu: 1500 + bar_sp_net_name: mtas-55-10-nw_bar_sp + bar_sp_net_mtu: 1500 + jinja: + # VM_flavor_SC: vMTAS_16-54 + # VM_flavor_PL: vMTAS_16-54 + VM_flavor_SC: vMTAS_4-27-200 + VM_flavor_PL: vMTAS_4-27 + VM_image_SC: MTASv_4_CXP9031366-R20C08 + VM_image_SC-1: MTASv_4_CXP9031366-R20C08 + VM_image_SC-2: MTASv_4_CXP9031366-R20C08 + PL_scheduling_policy: anti-affinity + om_net_name: mtas-55-10-nw_om_sp1 + om_net_mtu: 1500 + om_subnet_IPv4_name: mtas-55-10-nw_om_sp1_subnet_IPv4 + om_subnet_IPv4_address: 10.80.99.80/29 + om_subnet_IPv4_gateway_address: 10.80.99.81 + om_subnet_IPv6_name: mtas-55-10-nw_om_sp1_subnet_IPv6 + om_subnet_IPv6_address: 2001:1b70:8294:3e1a::/64 + om_subnet_IPv6_gateway_address: fe80::10:80:99:81 + vipfrontend1_net_name: mtas-55-10-nw_om_sp2 + vipfrontend1_net_mtu: 1500 + vipfrontend2_net_name: mtas-55-10-nw_sig_sp + vipfrontend2_net_mtu: 1500 + vipfrontend3_net_name: mtas-55-10-nw_bar_sp + vipfrontend3_net_mtu: 1500 diff --git a/automatique.task.yaml.example b/automatique.task.yaml.example new file mode 100644 index 0000000..c15910b --- /dev/null +++ b/automatique.task.yaml.example @@ -0,0 +1,34 @@ +config: + git-branch: "" #origin/release/1.16 + remote-host: mtas + lcmscript-path: "/n/tas/LCM_scripts_CT" + image-store: "/local/scratch/Images" + node: mtas-41-1 + r-state: R19E05 + arm-user: + arm-token: + +tasks: + - name: Build workflow release package + command: "standalone:workflow:build-package" + stored-args: [remote-host, lcmscript-path, git-branch] + provides: [workflow-package] + - name: Ensure image exists + command: "standalone:image:ensure-present" + stored-args: [remote-host, image-store, r-state] + - name: Upload image to glance + command: "standalone:atlas:upload-images" + stored-args: [remote-host, image-store, r-state, node] + - name: Prepare templates + command: "standalone:image:prepare-template" + stored-args: [remote-host, image-store, r-state, node] + - name: Generate onboard package + command: "standalone:workflow:generate-onboard-package" + stored-args: [remote-host, image-store, r-state, node, workflow-package] + provides: [onboard-package] + - name: Onboard package + command: "standalone:workflow:onboard-package" + stored-args: [remote-host, node, onboard-package] + - name: Cleanup + command: "standalone:cleanup-workplace" + stored-args: [remote-host, image-store, r-state, tmp-dir] diff --git a/composer.json b/composer.json index 5ffc459..10f8ab9 100644 --- a/composer.json +++ b/composer.json @@ -29,8 +29,10 @@ "symfony/console": "~4.0", "symfony/process": "~4.0", "symfony/yaml": "~4.0", + "twig/twig": "^3.0", "zendframework/zend-component-installer": "^2.1.1", "zendframework/zend-config-aggregator": "^1.0", + "zendframework/zend-http": "2.10.0", "zendframework/zend-json": "3.1.0", "zendframework/zend-servicemanager": "^3.3", "zendframework/zend-stdlib": "^3.1" diff --git a/composer.lock b/composer.lock index 469d1c1..3e18ec7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f71f8d197648c0e6aaf75d3877a47421", + "content-hash": "a21e30c1186a64be189bd5eb128f2561", "packages": [ { "name": "container-interop/container-interop", @@ -35,6 +35,7 @@ ], "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", "homepage": "https://github.com/container-interop/container-interop", + "abandoned": "psr/container", "time": "2017-02-14T19:40:03+00:00" }, { @@ -664,6 +665,70 @@ "homepage": "https://symfony.com", "time": "2019-09-11T15:41:19+00:00" }, + { + "name": "twig/twig", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "9b58bb8ac7a41d72fbb5a7dc643e07923e5ccc26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/9b58bb8ac7a41d72fbb5a7dc643e07923e5ccc26", + "reference": "9b58bb8ac7a41d72fbb5a7dc643e07923e5ccc26", + "shasum": "" + }, + "require": { + "php": "^7.2.9", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/debug": "^3.4|^4.2|^5.0", + "symfony/phpunit-bridge": "^4.4@dev|^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "homepage": "https://twig.symfony.com/contributors", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "time": "2019-11-15T20:38:32+00:00" + }, { "name": "zendframework/zend-component-installer", "version": "2.1.2", @@ -771,6 +836,106 @@ ], "time": "2018-04-04T20:37:31+00:00" }, + { + "name": "zendframework/zend-escaper", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-escaper.git", + "reference": "3801caa21b0ca6aca57fa1c42b08d35c395ebd5f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/3801caa21b0ca6aca57fa1c42b08d35c395ebd5f", + "reference": "3801caa21b0ca6aca57fa1c42b08d35c395ebd5f", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", + "zendframework/zend-coding-standard": "~1.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6.x-dev", + "dev-develop": "2.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Escaper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", + "keywords": [ + "ZendFramework", + "escaper", + "zf" + ], + "time": "2019-09-05T20:03:20+00:00" + }, + { + "name": "zendframework/zend-http", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-http.git", + "reference": "4b4983178693a8fdda53b0bbee58552e2d2b1ac0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-http/zipball/4b4983178693a8fdda53b0bbee58552e2d2b1ac0", + "reference": "4b4983178693a8fdda53b0bbee58552e2d2b1ac0", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "zendframework/zend-loader": "^2.5.1", + "zendframework/zend-stdlib": "^3.2.1", + "zendframework/zend-uri": "^2.5.2", + "zendframework/zend-validator": "^2.10.1" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.3", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-config": "^3.1 || ^2.6" + }, + "suggest": { + "paragonie/certainty": "For automated management of cacert.pem" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.10.x-dev", + "dev-develop": "2.11.x-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Http\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", + "keywords": [ + "ZendFramework", + "http", + "http client", + "zend", + "zf" + ], + "time": "2019-02-19T18:58:14+00:00" + }, { "name": "zendframework/zend-json", "version": "3.1.0", @@ -821,6 +986,51 @@ ], "time": "2018-01-04T17:51:34+00:00" }, + { + "name": "zendframework/zend-loader", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-loader.git", + "reference": "91da574d29b58547385b2298c020b257310898c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-loader/zipball/91da574d29b58547385b2298c020b257310898c6", + "reference": "91da574d29b58547385b2298c020b257310898c6", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", + "zendframework/zend-coding-standard": "~1.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6.x-dev", + "dev-develop": "2.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Loader\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Autoloading and plugin loading strategies", + "keywords": [ + "ZendFramework", + "loader", + "zf" + ], + "time": "2019-09-04T19:38:14+00:00" + }, { "name": "zendframework/zend-servicemanager", "version": "3.4.0", @@ -934,6 +1144,126 @@ "zf" ], "time": "2018-08-28T21:34:05+00:00" + }, + { + "name": "zendframework/zend-uri", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-uri.git", + "reference": "bfc4a5b9a309711e968d7c72afae4ac50c650083" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/bfc4a5b9a309711e968d7c72afae4ac50c650083", + "reference": "bfc4a5b9a309711e968d7c72afae4ac50c650083", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "zendframework/zend-escaper": "^2.5", + "zendframework/zend-validator": "^2.10" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", + "zendframework/zend-coding-standard": "~1.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev", + "dev-develop": "2.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Uri\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "A component that aids in manipulating and validating » Uniform Resource Identifiers (URIs)", + "keywords": [ + "ZendFramework", + "uri", + "zf" + ], + "time": "2019-10-07T13:35:33+00:00" + }, + { + "name": "zendframework/zend-validator", + "version": "2.12.2", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-validator.git", + "reference": "fd24920c2afcf2a70d11f67c3457f8f509453a62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/fd24920c2afcf2a70d11f67c3457f8f509453a62", + "reference": "fd24920c2afcf2a70d11f67c3457f8f509453a62", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.1", + "php": "^5.6 || ^7.0", + "zendframework/zend-stdlib": "^3.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0.8 || ^5.7.15", + "psr/http-message": "^1.0", + "zendframework/zend-cache": "^2.6.1", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-config": "^2.6", + "zendframework/zend-db": "^2.7", + "zendframework/zend-filter": "^2.6", + "zendframework/zend-http": "^2.5.4", + "zendframework/zend-i18n": "^2.6", + "zendframework/zend-math": "^2.6", + "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", + "zendframework/zend-session": "^2.8", + "zendframework/zend-uri": "^2.5" + }, + "suggest": { + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators", + "zendframework/zend-db": "Zend\\Db component, required by the (No)RecordExists validator", + "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", + "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages", + "zendframework/zend-i18n-resources": "Translations of validator messages", + "zendframework/zend-math": "Zend\\Math component, required by the Csrf validator", + "zendframework/zend-servicemanager": "Zend\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "zendframework/zend-session": "Zend\\Session component, ^2.8; required by the Csrf validator", + "zendframework/zend-uri": "Zend\\Uri component, required by the Uri and Sitemap\\Loc validators" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.12.x-dev", + "dev-develop": "2.13.x-dev" + }, + "zf": { + "component": "Zend\\Validator", + "config-provider": "Zend\\Validator\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Zend\\Validator\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "keywords": [ + "ZendFramework", + "validator", + "zf" + ], + "time": "2019-10-29T08:33:25+00:00" } ], "packages-dev": [ diff --git a/config/config.php b/config/config.php index fcf64ac..17f8d8c 100644 --- a/config/config.php +++ b/config/config.php @@ -13,6 +13,7 @@ $cacheConfig = [ ]; $aggregator = new ConfigAggregator([ + \Zend\Validator\ConfigProvider::class, // Include cache configuration new ArrayProvider($cacheConfig), diff --git a/src/App/Command/AutoOptions.php b/src/App/Command/AutoOptions.php new file mode 100644 index 0000000..31982a8 --- /dev/null +++ b/src/App/Command/AutoOptions.php @@ -0,0 +1,37 @@ +addOption($option, null, InputOption::VALUE_REQUIRED, "", null); + } + } + + /** + * @param InputInterface $input + * @return array + * @throws Exception + */ + protected function getFunctionArgs(InputInterface $input): array + { + $functionArgs = []; + foreach (self::OPTIONS as $key => $option) { + if (null === ($value = $input->getOption($option))) { + throw new Exception("Missing option: $option"); + } + $this->saveToShared($option, $value); + $functionArgs[] = $value; + } + return $functionArgs; + } +} diff --git a/src/App/Command/BuildPackage.php b/src/App/Command/BuildPackage.php index a526013..3213c0d 100644 --- a/src/App/Command/BuildPackage.php +++ b/src/App/Command/BuildPackage.php @@ -4,22 +4,24 @@ declare(strict_types=1); namespace App\Command; -use App\Downloader\SCP; use App\Service\Builder; use Doctrine\Common\Collections\Collection; use Exception; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Helper\ProgressBar; -use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Process\Process; class BuildPackage extends Command { - use SharedStorage; - const NAME = "standalone:build-package"; + use SharedStorage, + AutoOptions; + + const NAME = "standalone:workflow:build-package"; + const OPTIONS = [ + "remote-host", + "lcmscript-path", + "git-branch", + ]; /** @var Builder */ private $builder; @@ -33,17 +35,8 @@ class BuildPackage extends Command protected function configure() { - $this - ->setName(self::NAME) - ->setDescription('Build packages on remote host') - ->addOption("host", null, InputOption::VALUE_REQUIRED, "", false) - ->addOption("path", null, InputOption::VALUE_REQUIRED, "", false) - ; - } - - protected function saveToShared(string $name, $value) - { - $this->sharedStorage->set(sprintf("%s:%s", self::NAME, $name), $value); + $this->setName(self::NAME)->setDescription('Build packages on remote host'); + $this->configureOptions(); } /** @@ -53,30 +46,8 @@ class BuildPackage extends Command */ protected function execute(InputInterface $input, OutputInterface $output) { - $host = $input->getOption("host"); - $path = $input->getOption("path"); - if (!$host && !$path) { - throw new Exception("Missing arguments"); - } - $this->saveToShared("host", $host); - $this->saveToShared("path", $path); - $output->writeln("Building package:"); -// $helper = $this->getHelper('process'); -// $tableSection = $output->section(); -// $totalProgressSection = $output->section(); -// $itemProgressSection = $output->section(); -// $table = new Table($tableSection); -// $table->setHeaders([ -// 'Title', -// 'Pages', -// 'Size', -// ]); -// $table->render(); -// $totalProgress = new ProgressBar($totalProgressSection); -// $itemProgress = new ProgressBar($itemProgressSection); - $remotePackage = $this->builder->buildRemotePackage($host, $path); - $this->saveToShared("package", $remotePackage); - $downloader = new SCP(); - $downloader->download(sprintf("%s:%s", $host, $remotePackage), "."); + $funcArgs = $this->getFunctionArgs($input); + $remotePackage = $this->builder->buildRemotePackage(...$funcArgs); + $this->saveToShared("workflow-package", $remotePackage); } } diff --git a/src/App/Command/Cleanup.php b/src/App/Command/Cleanup.php new file mode 100644 index 0000000..57f3e32 --- /dev/null +++ b/src/App/Command/Cleanup.php @@ -0,0 +1,52 @@ +cleanup = $cleanup; + $this->sharedStorage = $storage; + parent::__construct(); + } + + protected function configure() + { + $this->setName(self::NAME)->setDescription('Cleanup workspace'); + $this->configureOptions();; + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @throws Exception + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $functionArgs = $this->getFunctionArgs($input); + $this->cleanup->cleanup(...$functionArgs); + } +} diff --git a/src/App/Command/CleanupFactory.php b/src/App/Command/CleanupFactory.php new file mode 100644 index 0000000..46955fb --- /dev/null +++ b/src/App/Command/CleanupFactory.php @@ -0,0 +1,21 @@ +get(ArrayCollection::class); + /** @var \App\Service\Cleanup $cleaner */ + $cleaner = $container->get(\App\Service\Cleanup::class); + return new Cleanup($cleaner, $sharedStorage); + } +} diff --git a/src/App/Command/ComplexTaskConfig.php b/src/App/Command/ComplexTaskConfig.php index 3b16832..fcce480 100644 --- a/src/App/Command/ComplexTaskConfig.php +++ b/src/App/Command/ComplexTaskConfig.php @@ -8,18 +8,28 @@ use App\Service\ConfigProvider; use Doctrine\Common\Collections\ArrayCollection; use Exception; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class ComplexTaskConfig extends Command { + use SharedStorage; + + const INPUT_OPTIONS = [ + 'remote-host' => 'Remote host used to build the packages', + 'lcmscript-path' => 'Where to find the lcmScripts to package', + 'image-store' => 'Where to store the downloaded images', + 'node' => 'Node to use in the generated package', + 'r-state' => 'R-state of vMTAS image', + 'git-branch' => 'Git branch to check out', + ]; + /** @var ConfigProvider */ private $configProvider; - /** @var ArrayCollection */ - private $sharedStorage; - public function __construct(ConfigProvider $configProvider, ArrayCollection $sharedStorage) { $this->configProvider = $configProvider; @@ -31,6 +41,36 @@ class ComplexTaskConfig extends Command { $this->setName('complex:task-config') ->setDescription('Build using task config'); + foreach (self::INPUT_OPTIONS as $option => $description) { + $this->addOption($option, null, InputOption::VALUE_REQUIRED, $description, false); + } + } + + private function getProgressBar(OutputInterface $output): ProgressBar + { + $totalProgress = new ProgressBar($output); + $totalProgress->setMessage($this->getDescription()); + $totalProgress->setFormat(" %message:-45s%\n%current%/%max% [%bar%] %percent:3s%%\n%elapsed:-20s% %memory:20s%"); + $totalProgress->setBarCharacter("●"); + $totalProgress->setEmptyBarCharacter("●"); + $totalProgress->setProgressCharacter("➤"); + return $totalProgress; + } + + private function parseSharedTaskConfig(array $config): void + { + foreach ($config as $key => $item) { + $this->saveToShared($key, $item); + } + } + + private function parseInputOptions(InputInterface $input): void + { + foreach (self::INPUT_OPTIONS as $option => $_) { + if (false !== ($value = $input->getOption($option))) { + $this->saveToShared($option, $value); + } + } } /** @@ -41,23 +81,30 @@ class ComplexTaskConfig extends Command protected function execute(InputInterface $input, OutputInterface $output) { $taskConfig = $this->configProvider->getTaskConfig(); + $this->parseSharedTaskConfig($taskConfig['config']); + $this->parseInputOptions($input); $app = $this->getApplication(); - foreach ($taskConfig as $task) { + $totalProgress = $this->getProgressBar($output); + foreach ($totalProgress->iterate($taskConfig['tasks']) as $task) { + $totalProgress->setMessage($task['name']); $cmd = $app->find($task['command']); $args = [ 'command' => $task['command'] ]; if (isset($task['args']) && is_array($task['args'])) { - $args += $task['args']; + foreach ($task['args'] as $key => $arg) { + $args["--${key}"] = $arg; + } } if (isset($task['stored-args']) && is_array($task['stored-args'])) { - foreach ($task['stored-args'] as $key => $storedArg) { - $args[$key] = $this->sharedStorage->get($storedArg); + foreach ($task['stored-args'] as $storedArg) { + $args["--${storedArg}"] = $this->sharedStorage->get("args:${storedArg}"); } } $cmd->run(new ArrayInput($args), $output); } + $output->writeln(""); } } diff --git a/src/App/Command/DownloadArmImage.php b/src/App/Command/DownloadArmImage.php new file mode 100644 index 0000000..538a4f4 --- /dev/null +++ b/src/App/Command/DownloadArmImage.php @@ -0,0 +1,44 @@ +downloader = $downloader; + parent::__construct(); + } + + protected function configure() + { + $this->setName(self::NAME) + ->setDescription('Generate onboard package') + ->addArgument("r-state", InputArgument::REQUIRED); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @throws Exception + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $rState = $input->getArgument("r-state"); + $this->downloader->downloadImage($rState); + } +} diff --git a/src/App/Command/DownloadArmImageFactory.php b/src/App/Command/DownloadArmImageFactory.php new file mode 100644 index 0000000..5cd95dc --- /dev/null +++ b/src/App/Command/DownloadArmImageFactory.php @@ -0,0 +1,18 @@ +get(ArmImageDownloader::class); + return new DownloadArmImage($downloader); + } +} diff --git a/src/App/Command/EnsureImagePresent.php b/src/App/Command/EnsureImagePresent.php new file mode 100644 index 0000000..10c4a69 --- /dev/null +++ b/src/App/Command/EnsureImagePresent.php @@ -0,0 +1,55 @@ +downloader = $downloader; + $this->sharedStorage = $storage; + parent::__construct(); + } + + protected function configure() + { + $this->setName(self::NAME)->setDescription('Generate onboard package'); + $this->configureOptions();; + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @throws Exception + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $functionArgs = $this->getFunctionArgs($input); + if ($this->downloader->isRemoteImageMissing(...$functionArgs)) { + $this->downloader->downloadImageToRemote(...$functionArgs); + } + $this->downloader->uncompressImage(...$functionArgs); + } +} diff --git a/src/App/Command/EnsureImagePresentFactory.php b/src/App/Command/EnsureImagePresentFactory.php new file mode 100644 index 0000000..0f55829 --- /dev/null +++ b/src/App/Command/EnsureImagePresentFactory.php @@ -0,0 +1,21 @@ +get(ArmImageDownloader::class); + /** @var ArrayCollection $sharedStorage */ + $sharedStorage = $container->get(ArrayCollection::class); + return new EnsureImagePresent($downloader, $sharedStorage); + } +} diff --git a/src/App/Command/GenerateOnboardPackage.php b/src/App/Command/GenerateOnboardPackage.php index ba61860..4608c2d 100644 --- a/src/App/Command/GenerateOnboardPackage.php +++ b/src/App/Command/GenerateOnboardPackage.php @@ -9,13 +9,23 @@ use Doctrine\Common\Collections\Collection; use Exception; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class GenerateOnboardPackage extends Command { - use SharedStorage; - const NAME = "standalone:generate-onboard-package"; + use SharedStorage, + AutoOptions; + + const NAME = "standalone:workflow:generate-onboard-package"; + const OPTIONS = [ + "remote-host", + "image-store", + "r-state", + "node", + "workflow-package", + ]; + + const PACKAGE_MATCH = "/.*Package ZIP file \[(.*?)\] has been created.*/msi"; /** @var Builder */ private $builder; @@ -29,12 +39,8 @@ class GenerateOnboardPackage extends Command protected function configure() { - $this - ->setName(self::NAME) - ->setDescription('Gen onboard package') - ->addOption("host", null, InputOption::VALUE_REQUIRED, "", false) - ->addOption("package", null, InputOption::VALUE_REQUIRED, "", false) - ; + $this->setName(self::NAME)->setDescription('Generate onboard package'); + $this->configureOptions();; } /** @@ -44,14 +50,9 @@ class GenerateOnboardPackage extends Command */ protected function execute(InputInterface $input, OutputInterface $output) { - $host = $input->getOption("host"); - $package = $input->getOption("package"); - if (!$host && !$package) { - throw new Exception("Missing arguments"); - } - $this->saveToShared("host", $host); - $this->saveToShared("path", $package); - $output->writeln("Generate onboard package:"); - $output->writeln("Got args: " . $input->getOption("host") . $input->getOption("package")); + $functionArgs = $this->getFunctionArgs($input); + $sshResult = $this->builder->createOnboardPackage(...$functionArgs); + preg_match(self::PACKAGE_MATCH, $sshResult, $matches); + $this->saveToShared("onboard-package", $matches[1]); } } diff --git a/src/App/Command/ListNodeImages.php b/src/App/Command/ListNodeImages.php new file mode 100644 index 0000000..cd9a41c --- /dev/null +++ b/src/App/Command/ListNodeImages.php @@ -0,0 +1,51 @@ +atlasManager = $atlasManager; + parent::__construct(); + } + + protected function configure() + { + $this->setName(self::NAME)->setDescription('Build packages on remote host'); + $this->configureOptions(); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @throws Exception + */ + protected function execute(InputInterface $input, OutputInterface $output) + { +// $funcArgs = $this->getFunctionArgs($input); + $images = $this->atlasManager->listImages(); + $table = new Table($output); + $table->addRows($images); + $table->render(); + } +} diff --git a/src/App/Command/ListNodeImagesFactory.php b/src/App/Command/ListNodeImagesFactory.php new file mode 100644 index 0000000..04cf1be --- /dev/null +++ b/src/App/Command/ListNodeImagesFactory.php @@ -0,0 +1,18 @@ +get(AtlasManager::class); + return new ListNodeImages($atlasManager); + } +} diff --git a/src/App/Command/OnboardPackage.php b/src/App/Command/OnboardPackage.php new file mode 100644 index 0000000..8507c50 --- /dev/null +++ b/src/App/Command/OnboardPackage.php @@ -0,0 +1,52 @@ +builder = $builder; + $this->sharedStorage = $storage; + parent::__construct(); + } + + protected function configure() + { + $this->setName(self::NAME)->setDescription('Generate onboard package'); + $this->configureOptions();; + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @throws Exception + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $functionArgs = $this->getFunctionArgs($input); + $this->builder->onboardPackage(...$functionArgs); + } +} diff --git a/src/App/Command/OnboardPackageFactory.php b/src/App/Command/OnboardPackageFactory.php new file mode 100644 index 0000000..0098e0f --- /dev/null +++ b/src/App/Command/OnboardPackageFactory.php @@ -0,0 +1,20 @@ +get(Builder::class); + $sharedStorage = $container->get(ArrayCollection::class); + return new OnboardPackage($builder, $sharedStorage); + } +} diff --git a/src/App/Command/PrepareTemplates.php b/src/App/Command/PrepareTemplates.php new file mode 100644 index 0000000..2aef00b --- /dev/null +++ b/src/App/Command/PrepareTemplates.php @@ -0,0 +1,53 @@ +templateGenerator = $templateGenerator; + $this->sharedStorage = $storage; + parent::__construct(); + } + + protected function configure() + { + $this->setName(self::NAME)->setDescription('Generate templates'); + $this->configureOptions(); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @throws Exception + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $funcArgs = $this->getFunctionArgs($input); + $this->templateGenerator->generateTemplates(...$funcArgs); + } +} diff --git a/src/App/Command/PrepareTemplatesFactory.php b/src/App/Command/PrepareTemplatesFactory.php new file mode 100644 index 0000000..0833d78 --- /dev/null +++ b/src/App/Command/PrepareTemplatesFactory.php @@ -0,0 +1,21 @@ +get(TemplateGenerator::class); + /** @var ArrayCollection $sharedStorage */ + $sharedStorage = $container->get(ArrayCollection::class); + return new PrepareTemplates($templateGenerator, $sharedStorage); + } +} diff --git a/src/App/Command/SharedStorage.php b/src/App/Command/SharedStorage.php index 5a4bda5..b834315 100644 --- a/src/App/Command/SharedStorage.php +++ b/src/App/Command/SharedStorage.php @@ -13,6 +13,6 @@ trait SharedStorage protected function saveToShared(string $name, $value) { - $this->sharedStorage->set(sprintf("%s:%s", self::NAME, $name), $value); + $this->sharedStorage->set(sprintf("args:%s", $name), $value); } } diff --git a/src/App/Command/UploadImage.php b/src/App/Command/UploadImage.php new file mode 100644 index 0000000..a72dfbe --- /dev/null +++ b/src/App/Command/UploadImage.php @@ -0,0 +1,53 @@ +atlasManager = $atlasManager; + $this->sharedStorage = $storage; + parent::__construct(); + } + + protected function configure() + { + $this->setName(self::NAME)->setDescription('Build packages on remote host'); + $this->configureOptions(); + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @throws Exception + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $funcArgs = $this->getFunctionArgs($input); + $this->atlasManager->ensureImageIsInGlance(...$funcArgs); + } +} diff --git a/src/App/Command/UploadImageFactory.php b/src/App/Command/UploadImageFactory.php new file mode 100644 index 0000000..b394d46 --- /dev/null +++ b/src/App/Command/UploadImageFactory.php @@ -0,0 +1,21 @@ +get(AtlasManager::class); + /** @var ArrayCollection $sharedStorage */ + $sharedStorage = $container->get(ArrayCollection::class); + return new UploadImage($atlasManager, $sharedStorage); + } +} diff --git a/src/App/ConfigProvider.php b/src/App/ConfigProvider.php index dfb6744..9a795c5 100644 --- a/src/App/ConfigProvider.php +++ b/src/App/ConfigProvider.php @@ -41,10 +41,20 @@ class ConfigProvider 'factories' => [ Service\Builder::class => Service\BuilderFactory::class, Service\ConfigProvider::class => Service\ConfigProviderFactory::class, + Service\ArmImageDownloader::class => Service\ArmImageDownloaderFactory::class, + Service\TemplateGenerator::class => Service\TemplateGeneratorFactory::class, + Service\Cleanup::class => Service\CleanupFactory::class, + Service\AtlasManager::class => Service\AtlasManagerFactory::class, - Command\BuildPackage::class => Command\BuildPackageFactory::class, Command\ComplexTaskConfig::class => Command\ComplexTaskConfigFactory::class, + Command\BuildPackage::class => Command\BuildPackageFactory::class, Command\GenerateOnboardPackage::class => Command\GenerateOnboardPackageFactory::class, + Command\OnboardPackage::class => Command\OnboardPackageFactory::class, + Command\DownloadArmImage::class => Command\DownloadArmImageFactory::class, + Command\EnsureImagePresent::class => Command\EnsureImagePresentFactory::class, + Command\PrepareTemplates::class => Command\PrepareTemplatesFactory::class, + Command\Cleanup::class => Command\CleanupFactory::class, + Command\UploadImage::class => Command\UploadImageFactory::class, ], ]; } @@ -61,6 +71,12 @@ class ConfigProvider 'lazy_commands' => [ Command\BuildPackage::NAME => Command\BuildPackage::class, Command\GenerateOnboardPackage::NAME => Command\GenerateOnboardPackage::class, + Command\OnboardPackage::NAME => Command\OnboardPackage::class, + Command\DownloadArmImage::NAME => Command\DownloadArmImage::class, + Command\EnsureImagePresent::NAME => Command\EnsureImagePresent::class, + Command\PrepareTemplates::NAME => Command\PrepareTemplates::class, + Command\Cleanup::NAME => Command\Cleanup::class, + Command\UploadImage::NAME => Command\UploadImage::class, ], ]; } diff --git a/src/App/Downloader/SCP.php b/src/App/Downloader/SCP.php index beab1b5..26a7936 100644 --- a/src/App/Downloader/SCP.php +++ b/src/App/Downloader/SCP.php @@ -11,7 +11,7 @@ class SCP implements DownloaderInterface public function download(string $remote, string $local): string { $process = new Process([ - 'scp', + 'scp', '-q', $remote, $local ]); diff --git a/src/App/Runner/RunnerInterface.php b/src/App/Runner/RunnerInterface.php index 13a9695..ff480f1 100644 --- a/src/App/Runner/RunnerInterface.php +++ b/src/App/Runner/RunnerInterface.php @@ -5,5 +5,5 @@ declare(strict_types=1); namespace App\Runner; interface RunnerInterface { - public function execute($command): string; + public function execute(string $command): string; } \ No newline at end of file diff --git a/src/App/Runner/SSH.php b/src/App/Runner/SSH.php index 00bc389..eec509b 100644 --- a/src/App/Runner/SSH.php +++ b/src/App/Runner/SSH.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Runner; +use Exception; use Symfony\Component\Process\Process; class SSH implements RunnerInterface @@ -15,17 +16,60 @@ class SSH implements RunnerInterface $this->hostname = $hostname; } - public function execute($command): string + /** + * @param string $command + * @return string + * @throws Exception + */ + public function execute(string $command): string { $process = new Process([ - 'ssh', + 'ssh', '-q', $this->hostname, $command ]); + $process->setTimeout(180); $process->run(); if (!$process->isSuccessful()) { - throw new \Exception("Command failed with: " . $process->getErrorOutput()); + throw new Exception("Command failed with: " . $process->getErrorOutput()); } return $process->getOutput(); } + + /** + * @param $src + * @param $dst + */ + public function copyToRemote($src, $dst) + { + $process = new Process([ + 'scp', '-q', + $src, + sprintf("%s:${dst}", $this->hostname) + ]); + $process->setTimeout(180); + $process->run(); + } + + /** + * @param $file + * @return string + * @throws Exception + */ + public function readFile($file): string + { + return $this->execute("cat ${file}"); + } + + /** + * @param $file + * @param $content + */ + public function saveFile($file, $content) + { + $tmpFile = tempnam(sys_get_temp_dir(), 'atmq'); + file_put_contents($tmpFile, $content); + $this->copyToRemote($tmpFile, $file); + unlink($tmpFile); + } } diff --git a/src/App/Service/ArmImageDownloader.php b/src/App/Service/ArmImageDownloader.php new file mode 100644 index 0000000..f4e5054 --- /dev/null +++ b/src/App/Service/ArmImageDownloader.php @@ -0,0 +1,73 @@ +httpClient = $httpClient; + $this->configProvider = $appConfig; + } + + public function downloadImage(string $rState): bool + { + $config = $this->configProvider->getTaskConfig()['config']; + $uri = self::ARM_URL . self::ARM_IMAGE_PATH . self::IMAGE_NAME; + $response = $this->httpClient->setStream(sprintf(self::IMAGE_NAME, $rState)) + ->setUri(sprintf($uri, $rState)) + ->setAuth($config['arm-user'], $config['arm-token']) + ->send() + ; + return $response->isSuccess(); + } + + public function downloadImageToRemote(string $host, string $path, string $rState): bool + { + $ssh = new SSH($host); + $ssh->execute("mkdir -p ${path}/${rState}"); + $imageUrl = sprintf(self::ARM_URL . self::ARM_IMAGE_PATH . self::IMAGE_NAME, $rState); + $imageFile = sprintf(self::IMAGE_NAME, $rState); + $ssh->execute("curl ${imageUrl} --silent --output ${path}/${rState}/${imageFile}"); + return true; + } + + public function isRemoteImageMissing(string $host, string $path, string $rState): bool + { + $ssh = new SSH($host); + try { + $ssh->execute("ls -1 ${path}/${rState}/" . sprintf(self::IMAGE_NAME, $rState)); + } catch (Exception $_) { + return true; + } + return false; + } + + public function uncompressImage(string $host, string $path, string $rState): bool + { + $ssh = new SSH($host); + $imageFile = sprintf(self::IMAGE_NAME, $rState); + $imageDir = "${path}/${rState}"; + $targetDir = "${imageDir}/uncompressed"; + $ssh->execute("rm -rf ${targetDir}"); + $ssh->execute("mkdir -p ${targetDir}"); + $ssh->execute("tar xf ${imageDir}/$imageFile -C ${targetDir}"); + return true; + } +} diff --git a/src/App/Service/ArmImageDownloaderFactory.php b/src/App/Service/ArmImageDownloaderFactory.php new file mode 100644 index 0000000..5d63d32 --- /dev/null +++ b/src/App/Service/ArmImageDownloaderFactory.php @@ -0,0 +1,19 @@ +get(ConfigProvider::class); + return new ArmImageDownloader($client, $appConfig); + } +} diff --git a/src/App/Service/AtlasManager.php b/src/App/Service/AtlasManager.php new file mode 100644 index 0000000..0fa92c1 --- /dev/null +++ b/src/App/Service/AtlasManager.php @@ -0,0 +1,120 @@ +envConfig = $envConfig; + } + + private static function openstackCommand(string $cmd): string + { + return sprintf("source openrc && %s", $cmd); + } + + /** + * @param SSH $ssh + * @return array|null + * @throws Exception + */ + public function listImages(SSH $ssh): ?array + { + $glanceOutput = $ssh->execute(self::openstackCommand(self::GLANCE_LIST)); + return Json::decode($glanceOutput, Json::TYPE_ARRAY); + } + + /** + * @param SSH $ssh + * @param string $imageName + * @return bool + * @throws Exception + */ + public function isImageInGlance(SSH $ssh, string $imageName): bool + { + $glanceImages = $this->listImages($ssh); + $filtered = array_filter($glanceImages, function($record) use ($imageName) { + return $record['Name'] == $imageName; + }); + $count = count($filtered); + if ($count > 1) { + throw new Exception("Multiple images exist with the same name, can't decide on what to use"); + } + return $count == 1; + } + + /** + * @param SSH $ssh + * @param string $imageFile + * @param string $dst + * @throws Exception + */ + public function copyImageToAtlas(SSH $ssh, string $imageFile, string $dst) + { + $ssh->execute("scp -q ${imageFile} ${dst}"); + } + + /** + * @param SSH $ssh + * @param string $imageName + * @return bool + */ + public function uploadImageToGlance(SSH $ssh, string $imageName): bool + { + try { + $imageFile = sprintf("/tmp/%s.qcow2", $imageName); + $out = $ssh->execute(self::openstackCommand(sprintf( + self::GLANCE_UPLOAD, + $imageFile, + $imageName + ))); + return true; + } catch (Exception $_) { + return false; + } + } + + /** + * @param string $host + * @param string $imageDir + * @param string $rState + * @param string $node + * @throws Exception + */ + public function ensureImageIsInGlance(string $host, string $imageDir, string $rState, string $node) + { + $vimCfg = $this->envConfig['node'][$node]['vim']; + + $workerSSH = new SSH($host); + $atlasSSH = new SSH($vimCfg['ssh-alias']); + + $imageName = sprintf(self::IMAGE_NAME_BASE, $rState); + if ($this->isImageInGlance($atlasSSH, $imageName)) { + return; + } + $imageSourceFile = sprintf("${imageDir}/${rState}/uncompressed/" . self::IMAGE_NAME_BASE . ".qcow2", $rState); + $atlasFileName = sprintf("/tmp/" . self::IMAGE_NAME_BASE . ".qcow2", $rState); + $imageDestinationFile = sprintf( + "%s@%s:${atlasFileName}", + $vimCfg['user'], + $vimCfg['address'] + ); + $this->copyImageToAtlas($workerSSH, $imageSourceFile, $imageDestinationFile); + $this->uploadImageToGlance($atlasSSH, $imageName); + $atlasSSH->execute("rm -rf ${atlasFileName}"); + } +} diff --git a/src/App/Service/AtlasManagerFactory.php b/src/App/Service/AtlasManagerFactory.php new file mode 100644 index 0000000..2f47da5 --- /dev/null +++ b/src/App/Service/AtlasManagerFactory.php @@ -0,0 +1,18 @@ +get(ConfigProvider::class); + $envConfig = $appConfig->getEnvironmentConfig(); + return new AtlasManager($envConfig); + } +} diff --git a/src/App/Service/Builder.php b/src/App/Service/Builder.php index 745d4ce..e948ed1 100644 --- a/src/App/Service/Builder.php +++ b/src/App/Service/Builder.php @@ -4,24 +4,123 @@ declare(strict_types=1); namespace App\Service; +use App\Command\SharedStorage; use App\Runner\SSH; +use Doctrine\Common\Collections\ArrayCollection; use Exception; class Builder { - const PACKAGE_BUILDER_SCRIPT = "prepare_package.sh"; + use SharedStorage; + + const WORKFLOW_PACKAGE_BUILDER_SCRIPT = "prepare_package.sh"; + const VNF_PACKAGE_CREATOR_SCRIPT = "vnfPackageCreator_mtas.py"; + + const FULL_PKG = "CXP9034815_1"; + const WF_PGK = "CXP9034788_1"; + const VNF_PGK_REPO_PATH = "/vnflcm-ext/current/vnf_package_repo"; + + /** @var ConfigProvider */ + private $configProvider; + + public function __construct(ConfigProvider $configProvider, ArrayCollection $sharedStorage) + { + $this->configProvider = $configProvider; + $this->sharedStorage = $sharedStorage; + } /** - * @param $host - * @param $path - * @return string Generated filename on remote host + * @param SSH $ssh + * @param string $path + * @param string $branch * @throws Exception */ - public function buildRemotePackage($host, $path): string + private function checkoutBranch(SSH $ssh, string $path, string $branch) + { + $ssh->execute("cd ${path} && git fetch origin && git checkout ${branch}"); + } + + /** + * @param string $host + * @param string $path + * @param string|null $branch + * @return string + * @throws Exception + */ + public function buildRemotePackage(string $host, string $path, ?string $branch = null): string { $runner = new SSH($host); - $out = $runner->execute(sprintf("%s/%s", $path, self::PACKAGE_BUILDER_SCRIPT)); + if ($branch) { + $this->checkoutBranch($runner, $path, $branch); + } + $out = $runner->execute(sprintf("%s/%s", $path, self::WORKFLOW_PACKAGE_BUILDER_SCRIPT)); $lines = explode("\n", trim($out)); return array_pop($lines); } + + /** + * @param string $host + * @param string $imageStore + * @param string $rState + * @param string $node + * @param string $package + * @return string + * @throws Exception + */ + public function createOnboardPackage(string $host, string $imageStore, string $rState, string $node, string $package): string + { + $swDirectory = "${imageStore}/${rState}/uncompressed"; + $ssh = new SSH($host); + $workDir = trim($ssh->execute("mktemp -d")); + $this->saveToShared("tmp-dir", $workDir); + $packageName = basename($package); + preg_match(sprintf("/%s-(R[0-9]+[A-Z]+[0-9]+)\.tar\.gz/", self::FULL_PKG), $packageName, $matches); + $rState = $matches[1]; + $ssh->execute("mv ${package} ${workDir}"); + $ssh->execute("tar -xzf ${workDir}/${packageName} -C ${workDir}"); + $ssh->execute(sprintf("tar -xzf ${workDir}/%s-${rState}.tar.gz -C ${workDir}", self::WF_PGK)); + try { + $ssh->execute("ls -l ${swDirectory}/prepareHot.bash"); + } catch(Exception $_) { + return $ssh->execute(sprintf( + "cd ${workDir} && ./%s -sw %s -hf %s -wf . -i %s 2>&1", + self::VNF_PACKAGE_CREATOR_SCRIPT, + $swDirectory, + sprintf("$swDirectory/%s/%s", TemplateGenerator::GENERATED_HOT_DIR, TemplateGenerator::APP_STACK_DIR), + $node + )); + } + $cmd = sprintf( + "cd ${workDir} && ./%s -sw %s -wf . -i %s 2>&1", + self::VNF_PACKAGE_CREATOR_SCRIPT, + $swDirectory, + $node + ); + var_dump($cmd); + try { + return $ssh->execute($cmd); + } catch (Exception $_) { + throw new Exception("Couldn't create on-board package! Mismatching Workflow-MTAS packages?"); + } + } + + /** + * @param string $host + * @param string $node + * @param string $package + * @throws Exception + */ + public function onboardPackage(string $host, string $node, string $package): void + { + $envConfig = $this->configProvider->getEnvironmentConfig(); + $lcmConfig = $envConfig['node'][$node]['vnf-lcm']; + $lcmUri = sprintf("%s@%s", $lcmConfig['user'], $lcmConfig['address']); + $pkgName = basename($package); + + $runner = new SSH($host); + $sshOptions = "-o StrictHostKeyChecking=no"; + $runner->execute(sprintf("scp $sshOptions $package $lcmUri:%s/", self::VNF_PGK_REPO_PATH)); + $runner->execute(sprintf("ssh $sshOptions $lcmUri 'cd %s && unzip -o $pkgName'", self::VNF_PGK_REPO_PATH)); +// $runner->execute(sprintf("ssh $sshOptions $lcmUri 'rm -f %s/$pkgName'", self::VNF_PGK_REPO_PATH)); + } } diff --git a/src/App/Service/BuilderFactory.php b/src/App/Service/BuilderFactory.php index 436c77b..9013b37 100644 --- a/src/App/Service/BuilderFactory.php +++ b/src/App/Service/BuilderFactory.php @@ -4,12 +4,17 @@ declare(strict_types=1); namespace App\Service; +use Doctrine\Common\Collections\ArrayCollection; use Interop\Container\ContainerInterface; class BuilderFactory { public function __invoke(ContainerInterface $container): Builder { - return new Builder(); + /** @var ConfigProvider $configProvider */ + $configProvider = $container->get(ConfigProvider::class); + /** @var ArrayCollection $sharedStorage */ + $sharedStorage = $container->get(ArrayCollection::class); + return new Builder($configProvider, $sharedStorage); } } diff --git a/src/App/Service/Cleanup.php b/src/App/Service/Cleanup.php new file mode 100644 index 0000000..3e44abe --- /dev/null +++ b/src/App/Service/Cleanup.php @@ -0,0 +1,33 @@ +configProvider = $configProvider; + } + + /** + * @param $host + * @param $imageStore + * @param $rState + * @param $tmpDir + * @throws Exception + */ + public function cleanup($host, $imageStore, $rState, $tmpDir) + { + $ssh = new SSH($host); + $ssh->execute("rm -rf ${imageStore}/${rState}/uncompressed"); + $ssh->execute("rm -rf ${tmpDir}"); + } +} diff --git a/src/App/Service/CleanupFactory.php b/src/App/Service/CleanupFactory.php new file mode 100644 index 0000000..98790c6 --- /dev/null +++ b/src/App/Service/CleanupFactory.php @@ -0,0 +1,17 @@ +get(ConfigProvider::class); + return new Cleanup($configProvider); + } +} diff --git a/src/App/Service/ConfigProvider.php b/src/App/Service/ConfigProvider.php index 5537fa0..ab5d9cb 100644 --- a/src/App/Service/ConfigProvider.php +++ b/src/App/Service/ConfigProvider.php @@ -47,18 +47,18 @@ class ConfigProvider throw new InvalidArgumentException("Couldn't locate $filename"); } - private function readConfigFile($configFile): ?iterable + private function readConfigFile($configFile): ?array { $configFile = $this->findConfigFile($configFile); return Yaml::parseFile($configFile); } - public function getEnvironmentConfig(): ?iterable + public function getEnvironmentConfig(): ?array { return $this->readConfigFile($this->envFile); } - public function getTaskConfig(): ?iterable + public function getTaskConfig(): ?array { return $this->readConfigFile($this->taskFile); } diff --git a/src/App/Service/TemplateGenerator.php b/src/App/Service/TemplateGenerator.php new file mode 100644 index 0000000..2154760 --- /dev/null +++ b/src/App/Service/TemplateGenerator.php @@ -0,0 +1,160 @@ +envConfig = $envConfig; + $this->taskConfig = $taskConfig; + } + + /** + * @param string $template + * @param array $data + * @return string + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError + */ + private function fillJinjaTemplate(string $template, array $data): string + { + $twig = new Environment(new ArrayLoader(['env' => $template])); + return $twig->render('env', $data); + } + + /** + * @param SSH $ssh + * @param string $envFile + * @param string $node + * @param string $key + * @throws Exception + */ + private function fillEnv(SSH $ssh, string $envFile, string $node, string $key) + { + $fileContents = $ssh->readFile($envFile); + $unfilledEnv = Yaml::parse($fileContents); + $mergedConfig = $this->envConfig['node'][$node]['env']['shared'] + $this->envConfig['node'][$node]['env'][$key]; + foreach ($unfilledEnv['parameters'] as $key => &$value) { + if (array_key_exists($key, $mergedConfig)) { + $value = $mergedConfig[$key]; + } + } + $ssh->saveFile($envFile, Yaml::dump($unfilledEnv, 2, 4, Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE)); + } + + /** + * @param SSH $ssh + * @param string $envFile + * @param string $node + * @throws LoaderError + * @throws RuntimeError + * @throws SyntaxError + * @throws Exception + */ + private function prefillJinjaEnv(SSH $ssh, string $envFile, string $node) + { + $unfilledEnv = $ssh->readFile($envFile); + $data = $this->envConfig['node'][$node]['env']['shared'] + $this->envConfig['node'][$node]['env']['jinja']; + $filledTemplate = $this->fillJinjaTemplate($unfilledEnv, $data); + $ssh->saveFile($envFile, $filledTemplate); + } + + /** + * @param SSH $ssh + * @param string $workDir + * @param string $node + * @throws Exception + */ + private function generateLegacyTemplates(SSH $ssh, string $workDir, string $node) + { + $params = []; + foreach($this->envConfig['node'][$node]['template-params']['legacy'] as $k => $v) { + $params[] = "$k $v"; + } + $ssh->execute(sprintf( + "cd ${workDir} && ./%s %s", + self::LEGACY_GENERATOR_SCRIPT, + implode(" ", $params) + )); + $this->fillEnv( + $ssh, + sprintf("$workDir/%s", self::LEGACY_ENV_FILE), + $node, + 'legacy' + ); + } + + /** + * @param SSH $ssh + * @param string $workDir + * @param string $node + * @throws Exception + */ + private function generateJinjaTemplates(SSH $ssh, string $workDir, string $node) + { + $fileContents = $ssh->readFile(sprintf("%s/%s", $workDir, self::CONTEXT_TEMPLATE_FILE)); + $filledContext = $this->fillJinjaTemplate( + $fileContents, + $this->envConfig['node'][$node]['template-params']['jinja'] + ); + $ssh->saveFile("${workDir}/" . self::FILLED_CONTEXT_FILE, $filledContext); + + $ssh->execute(sprintf( + "${workDir}/%s $workDir/%s $workDir/%s", + self::JINJA_GENERATOR_SCRIPT, + self::GENERATED_HOT_DIR, + self::FILLED_CONTEXT_FILE)); + + $envFile = sprintf("$workDir/%s/%s/%s", self::GENERATED_HOT_DIR, self::APP_STACK_DIR, self::JINJA_ENV_FILE); + $this->prefillJinjaEnv($ssh, $envFile, $node); + $this->fillEnv($ssh, $envFile, $node, 'jinja'); + } + + /** + * @param string $host + * @param string $imageDir + * @param string $rState + * @param string $node + * @throws Exception + */ + public function generateTemplates(string $host, string $imageDir, string $rState, string $node) + { + $workDir = "${imageDir}/${rState}/uncompressed"; + $ssh = new SSH($host); + try { + $ssh->execute("ls -1 $workDir/" . self::LEGACY_GENERATOR_SCRIPT); + $this->generateLegacyTemplates($ssh, $workDir, $node); + } catch (Exception $_) { + $this->generateJinjaTemplates($ssh, $workDir, $node); + } + } +} diff --git a/src/App/Service/TemplateGeneratorFactory.php b/src/App/Service/TemplateGeneratorFactory.php new file mode 100644 index 0000000..8564945 --- /dev/null +++ b/src/App/Service/TemplateGeneratorFactory.php @@ -0,0 +1,19 @@ +get(ConfigProvider::class); + $envConfig = $configProvider->getEnvironmentConfig(); + $taskConfig = $configProvider->getTaskConfig(); + return new TemplateGenerator($envConfig, $taskConfig); + } +}