/// <inheritdoc/> public override async Task RunAsync(CommandLine commandLine) { if (commandLine.HasHelpOption) { Help(); Program.Exit(0); } Console.WriteLine(); // Cluster prepare/setup uses the [ProfileClient] to retrieve secrets and profile values. // We need to inject an implementation for [PreprocessReader] so it will be able to // perform the lookups. NeonHelper.ServiceContainer.AddSingleton <IProfileClient>(new ProfileClient()); // Handle the [--remove-templates] option. if (commandLine.HasOption("--remove-templates")) { Console.WriteLine("Removing cached virtual machine templates."); foreach (var fileName in Directory.GetFiles(KubeHelper.NodeImageFolder, "*.*", SearchOption.TopDirectoryOnly)) { File.Delete(fileName); } } var nodeImageUri = commandLine.GetOption("--node-image-uri"); var nodeImagePath = commandLine.GetOption("--node-image-path"); var debug = commandLine.HasOption("--debug"); var baseImageName = commandLine.GetOption("--base-image-name"); var clusterspace = commandLine.GetOption("--clusterspace"); var headendUri = commandLine.GetOption("--headend-uri") ?? KubeConst.NeonCloudHeadendUri; var maxParallelOption = commandLine.GetOption("--max-parallel", "6"); var disablePending = commandLine.HasOption("--disable-pending"); if (!int.TryParse(maxParallelOption, out var maxParallel) || maxParallel <= 0) { Console.Error.WriteLine($"*** ERROR: [--max-parallel={maxParallelOption}] is not valid."); Program.Exit(1); } if (debug && string.IsNullOrEmpty(baseImageName)) { Console.Error.WriteLine($"*** ERROR: [--base-image-name] is required for [--debug] mode."); Program.Exit(1); } // Implement the command. if (KubeHelper.CurrentContext != null) { Console.Error.WriteLine("*** ERROR: You are logged into a cluster. You need to logout before preparing another."); Program.Exit(1); } if (commandLine.Arguments.Length == 0) { Console.Error.WriteLine($"*** ERROR: CLUSTER-DEF expected."); Program.Exit(1); } // Obtain the cluster definition. var clusterDefPath = commandLine.Arguments[0]; var clusterDefinition = (ClusterDefinition)null; ClusterDefinition.ValidateFile(clusterDefPath, strict: true); clusterDefinition = ClusterDefinition.FromFile(clusterDefPath, strict: true); // Do a quick sanity check to ensure that the hosting environment has enough // resources (memory and disk) to actually host the cluster. using (var cluster = new ClusterProxy(clusterDefinition, new HostingManagerFactory())) { var status = await cluster.GetResourceAvailabilityAsync(); if (!status.CanBeDeployed) { Console.Error.WriteLine(); Console.Error.WriteLine($"*** ERROR: Insufficent resources available to deploy cluster."); Console.Error.WriteLine(); foreach (var entity in status.Constraints.Keys .OrderBy(key => key, StringComparer.InvariantCultureIgnoreCase)) { Console.Error.WriteLine(); Console.Error.WriteLine($"{entity}:"); foreach (var constraint in status.Constraints[entity]) { Console.Error.WriteLine($" {constraint.ResourceType.ToString().ToUpperInvariant()}: {constraint.Details}"); } } Console.Error.WriteLine(); Program.Exit(1); } } if (KubeHelper.IsOnPremiseHypervisorEnvironment(clusterDefinition.Hosting.Environment)) { // Use the default node image for the hosting environment unless [--node-image-uri] // or [--node-image-path] was specified. if (string.IsNullOrEmpty(nodeImageUri) && string.IsNullOrEmpty(nodeImagePath)) { nodeImageUri = KubeDownloads.GetDefaultNodeImageUri(clusterDefinition.Hosting.Environment); } } // Parse any specified package cache endpoints. var packageCaches = commandLine.GetOption("--package-caches", null); var packageCacheEndpoints = new List <IPEndPoint>(); if (!string.IsNullOrEmpty(packageCaches)) { foreach (var item in packageCaches.Split(' ', StringSplitOptions.RemoveEmptyEntries)) { if (!NetHelper.TryParseIPv4Endpoint(item, out var endpoint)) { Console.Error.WriteLine($"*** ERROR: [{item}] is not a valid package cache IPv4 endpoint."); Program.Exit(1); } packageCacheEndpoints.Add(endpoint); } } // Create and run the cluster prepare controller. var controller = KubeSetup.CreateClusterPrepareController( clusterDefinition, nodeImageUri: nodeImageUri, nodeImagePath: nodeImagePath, maxParallel: maxParallel, packageCacheEndpoints: packageCacheEndpoints, unredacted: commandLine.HasOption("--unredacted"), debugMode: debug, baseImageName: baseImageName, clusterspace: clusterspace, neonCloudHeadendUri: headendUri); controller.DisablePendingTasks = disablePending; controller.StatusChangedEvent += status => { status.WriteToConsole(); }; switch (await controller.RunAsync()) { case SetupDisposition.Succeeded: var pendingGroups = controller.GetPendingGroups(); if (pendingGroups.Count > 0) { Console.WriteLine($"*** ERROR: [{pendingGroups.Count}] pending task groups have not been awaited:"); Console.WriteLine(); foreach (var groupName in pendingGroups) { Console.WriteLine($" {groupName}"); } Program.Exit(1); } Console.WriteLine(); Console.WriteLine($" [{clusterDefinition.Name}] cluster is prepared."); Console.WriteLine(); Program.Exit(0); break; case SetupDisposition.Cancelled: Console.WriteLine(); Console.WriteLine(" *** CANCELLED: Cluster prepare was cancelled."); Console.WriteLine(); Program.Exit(1); break; case SetupDisposition.Failed: Console.WriteLine(); Console.WriteLine(" *** ERROR: Cluster prepare has failed. Examine the logs here:"); Console.WriteLine(); Console.WriteLine($" {KubeHelper.LogFolder}"); Console.WriteLine(); Program.Exit(1); break; default: throw new NotImplementedException(); } await Task.CompletedTask; }