private static async Task <int> EnsureRegistratorContainerIsRunning(string eurekaServerUrl, string ipAddress) { var registratorImageUri = await EcrResources.RegistratorLatestImageUrl.EnsureImageIsPulled(); if (!registratorImageUri.WasSuccessful) { return(1); } var registratorContainerName = "registrator-marvel"; // Build arguments list for `docker create` call so we can hash them and ensure an existing container is compatible. Argument pairs are combined into one argument for readability. var dockerCreateArgs = new List <string> { "--net=host", "--volume=/var/run/docker.sock:/tmp/docker.sock" }; var createContainerResult = await DockerCommands.GetOrCreateContainerWithName( registratorContainerName, registratorImageUri.Value, dockerCreateArgs, $"-ttl 30 -ttl-refresh 15 -ip {ipAddress} -require-label {FixEurekaServerUrlScheme(eurekaServerUrl ?? DefaultEurekaServerUrl)}"); var container = createContainerResult.Container; var isNewContainer = createContainerResult.IsNewContainer; if (!container.IsRunning) { Output.Info($"Starting container {registratorContainerName}"); var result = await CommandUtilities.RunCommandAsync("docker", $"start {container.ContainerId}", throwOnErrorExitCode : false); if (result.ExitCode != 0) { var stdErr = string.Join("\n", result.StandardError); // Message is `Bind for 0.0.0.0:5000 failed: port is already allocated`. trimming the port portion in case the port changes. // if (stdErr.Contains("failed: port is already allocated")) // { // Output.Info("Webapp port is already in use. Attempting to stop other container using port."); // // Find other containers using a partial match on suffix. This corresponds to the naming scheme defined in MarvelMicroserviceConfig. // var containers = await DockerCommands.GetContainersByName("marvel-dev"); // var otherContainer = containers.FirstOrDefault(c => c.IsRunning); // if (otherContainer == null) // { // Output.Error("Unable to find running container using same port."); // Output.Error($"Failed to start container {containerName}. StandardError={stdErr}"); // return 1; // } // Output.Info($"Stopping container {otherContainer.ContainerId}"); // await DockerCommands.StopContainer(otherContainer); // Output.Info($"Starting container {containerName} again"); // var restartResult = await ProcessEx.RunAsync("docker", $"start {container.ContainerId}"); // if (restartResult.ExitCode != 0) // { // Output.Error($"Failed to restart container {containerName}. StandardError={stdErr}"); // return result.ExitCode; // } // } // else // { Output.Error($"Failed to start container {registratorContainerName}. StandardError={stdErr}"); return(result.ExitCode); // } } // Ensure the container doesn't immediately exit. // TODO Bumped this up for registrator specifically to ensure eureka host is valid. Might want to verify by scanning logs // that this did in fact start up properly. Thread.Sleep(500); var runningContainer = await DockerCommands.GetContainerByName(registratorContainerName); if (!runningContainer.IsRunning) { Output.Error($"Container {registratorContainerName} stopped unexpectedly. Check container logs by running {DockerCommands.GetLogsForContainerCommand(runningContainer)}."); return(1); } } // TODO - ensure registrator is ready? pause? Output.Info($"Container {registratorContainerName} is running."); return(0); }
private static void LogValidationErrors(List <string> errors) { errors.ForEach(error => Output.Error($"Argument validation error: {error}")); }
public static async Task <int> Launch(MarvelMicroserviceConfig config, LaunchOptions launchOptions) { var registratorRunResultTask = EnsureRegistratorContainerIsRunning(launchOptions.EurekaServer, launchOptions.LocalIpAddress); var buildImageUriTask = EcrResources.DotnetMicroserviceBuildImageUrl.EnsureImageIsPulled(); var registratorRunResult = await registratorRunResultTask; if (registratorRunResult != 0) { return(registratorRunResult); } var buildImageUri = await buildImageUriTask; if (!buildImageUri.WasSuccessful) { return(1); } // # TODO include a hash of the tools version in the container name to ensure containers are recreated after tools update(?) var containerName = config.DevContainerName; var dockerTaskScriptPath = ContainerPaths.DockerTaskScriptPath; // Build arguments list for `docker create` call so we can hash them and ensure an existing container is compatible. Argument pairs are combined into one argument for readability. var hostname = launchOptions.Host ?? await NetworkUtility.GetFriendlyHostName(); var labelArgs = LabelUtilities.FormatLabelsAsArguments(await new LabelBuilder(config).GetLabelsForLocalDev(hostname)); var dockerCreateArgs = labelArgs.Concat(new List <string> { "-p 5000:5000", "--dns-search=agilesports.local", $"-e \"{ProjectNameEnvVariable}={config.ProjectName}\"", $"-e \"{SolutionNameEnvVariable}={config.SolutionName}\"", $"-e \"{WebappDirEnvVariable}={ContainerPaths.GetWebappDirectory(config)}\"", $"-e \"{BrowserAppDirEnvVariable}={ContainerPaths.GetBrowserAppDirectory(config)}\"", // This could probably live in the image $"-e ASPNETCORE_ENVIRONMENT=Development", $"-v {config.BaseDirectory}:{ContainerPaths.MountedSourceDirectory}", }); if (launchOptions.UseSharedCache) { dockerCreateArgs = dockerCreateArgs.Concat(GetCacheVolumeArgs()); } var createContainerResult = await DockerCommands.GetOrCreateContainerWithName( containerName, buildImageUri.Value, dockerCreateArgs.ToList(), $"/bin/bash {dockerTaskScriptPath} hang"); var container = createContainerResult.Container; var isNewContainer = createContainerResult.IsNewContainer; if (!container.IsRunning) { Output.Info($"Starting container {containerName}"); var result = await CommandUtilities.RunCommandAsync("docker", $"start {container.ContainerId}", throwOnErrorExitCode : false); // Output.Info("StdErr=" + string.Join("\n", result.StandardError)); // Output.Info("StdOut=" + string.Join("\n", result.StandardOutput)); if (result.ExitCode != 0) { var stdErr = string.Join("\n", result.StandardError); // Message is `Bind for 0.0.0.0:5000 failed: port is already allocated`. trimming the port portion in case the port changes. if (stdErr.Contains("failed: port is already allocated")) { Output.Info("Webapp port is already in use. Attempting to stop other container using port."); // Find other containers using a partial match on suffix. This corresponds to the naming scheme defined in MarvelMicroserviceConfig. var containers = await DockerCommands.GetContainersByName("marvel-dev"); var otherContainer = containers.FirstOrDefault(c => c.IsRunning); if (otherContainer == null) { Output.Error("Unable to find running container using same port."); Output.Error($"Failed to start container {containerName}. StandardError={stdErr}"); return(1); } Output.Info($"Stopping container {otherContainer.ContainerId}"); await DockerCommands.StopContainer(otherContainer); Output.Info($"Starting container {containerName} again"); var restartResult = await ProcessEx.RunAsync("docker", $"start {container.ContainerId}"); if (restartResult.ExitCode != 0) { Output.Error($"Failed to restart container {containerName}. StandardError={stdErr}"); return(result.ExitCode); } } else { Output.Error($"Failed to start container {containerName}. StandardError={stdErr}"); return(result.ExitCode); } } // TODO only perform this check after failed `docker exec` commands, for better reporting? // Ensure the container doesn't immediately exit. Thread.Sleep(10); var runningContainer = await DockerCommands.GetContainerByName(containerName); if (!runningContainer.IsRunning) { Output.Error($"Container {containerName} stopped unexpectedly. Check container logs."); return(1); } } if (isNewContainer) { Output.Info($"Attaching to container {containerName} to run first time launch command on new container"); var code = CommandUtilities.ExecuteCommand("docker", $"exec -i {container.ContainerId} /bin/bash {dockerTaskScriptPath} firstTimeLaunch"); if (code != 0) { Output.Info($"First time startup command failed. Removing container."); await DockerCommands.RemoveContainer(container); return(code); } } else { Output.Info($"Attaching to container {containerName} to run relaunch command on existing container"); var code = CommandUtilities.ExecuteCommand("docker", $"exec -i {container.ContainerId} /bin/bash {dockerTaskScriptPath} relaunch"); if (code != 0) { return(code); } } Output.Info($"Container {containerName} launched and ready to run"); Output.Info($"Using hostname: {hostname}"); Output.Info($"If debugging from VS Code, switch to the Debug Console (Cmd+Shift+Y / Ctrl+Shift+Y) for app and watch process logs"); return(0); }
internal static async Task <int> Publish(MarvelMicroserviceConfig config, IBuildConfigurationBuilder configBuilder, IConfigurationFileMerger configMerger, IConfigurationFileUploader configUploader, IConfigurationFileValidator configValidator, IStaticAssetProcessor staticAssetProcessor, string nugetApiKey, string awsAccessKey, string awsAccessSecret, string branch, string gitSha, string buildNumber, bool mergeAndUploadServiceConfig, bool mergeServiceConfig) { Security.UseAwsCredentials(awsAccessKey, awsAccessSecret); var publishImage = ImageNameBuilder.CreateImageNameAndTag( config.ServiceName, branch, gitSha, DateTime.UtcNow, buildNumber); string[] serviceConfigFiles = null; if (mergeAndUploadServiceConfig || mergeServiceConfig) { GenerateBuildFile(configBuilder, config.BuildConfigFilePath, gitSha, branch, publishImage.FullPath, buildNumber); serviceConfigFiles = await MergeAllServiceConfigFiles(configMerger, config.SourceDirectory, config.ServiceConfigFileName, config.BuildConfigFilePath); var configIsValid = await ValidateAllServiceConfigFiles(configValidator, config.SourceDirectory, serviceConfigFiles); if (!configIsValid) { Output.Error("Invalid service configuration."); return(1); } } var exitCode = await BuildCommand.Build(config, publishImage.FullPath, new BuildConfig { BranchName = branch, BuildNumber = buildNumber, }, staticAssetProcessor); if (exitCode != 0) { return(exitCode); } try { exitCode = PublishClientPackage(config, nugetApiKey, awsAccessKey, awsAccessSecret, branch, gitSha, buildNumber); if (exitCode != 0) { return(exitCode); } // Publish to ECR Output.Info($"Publishing {publishImage.FullPath}"); await Security.EnsureAuthenticatedWithEcr(); exitCode = CommandUtilities.ExecuteCommand("docker", $"push {publishImage.FullPath}"); if (exitCode != 0) { return(exitCode); } } finally { // TODO always remove image, even on publish failure await CommandUtilities.RunCommandAsync("docker", $"rmi {publishImage.FullPath}", errorMessage : $"Failed to remove image {publishImage.FullPath}."); Output.Info($"Removed local image {publishImage.FullPath}"); } try { if (mergeAndUploadServiceConfig && serviceConfigFiles != null) { await UploadAllServiceConfigFiles(configUploader, config.SourceDirectory, serviceConfigFiles, publishImage.Tag); } } catch (Exception ex) { Output.Error($"Unable to upload service configuration files. Error: {ex.Message}"); return(1); } File.WriteAllText(Path.Combine(config.WebappDirectory, "PublishedImageUrl.txt"), publishImage.FullPath); Output.Info("Publish successful"); return(0); }