private static async Task <bool> IsContainerUpToDateWithImage(DockerCommands.ContainerInfo container, string imageName) { if (container == null) { throw new ArgumentNullException("container"); } var imageInfo = await DockerCommands.GetImageInfoByName(imageName); return(container.CreatedAt >= imageInfo.CreatedAt); }
protected override async Task <int> Run(MarvelMicroserviceConfig config) { var launchOptionsResult = await _launchOptions.ProcessOptions(); if (!launchOptionsResult.AreValid) { return(1); } var exitCode = await LaunchCommand.Launch(config, launchOptionsResult.Value); if (exitCode != 0) { return(exitCode); } var containerName = config.DevContainerName; var container = await DockerCommands.GetContainerByName(containerName); if (container?.IsRunning != true) { Output.Info($"Could not find running container {containerName}"); return(1); } // Call basic command to see if we're executing in a TTY var ttyTest = await CommandUtilities.RunCommandAsync("docker", $"exec -it {container.ContainerId} /bin/bash -c echo hi", throwOnErrorExitCode : false); var isTty = true; if (ttyTest.ExitCode != 0) { var stdErr = string.Join("\n", ttyTest.StandardError); if (!stdErr.Contains("input device is not a TTY")) { throw new Exception($"Unexpected exception encounterd checking for TTY StandardError={stdErr}"); } isTty = false; } Output.Info($"Attaching to container {containerName}"); // Use TTY option when available so that Ctrl+C in the terminal kills the process inside the container as well. Option cannot be used during integration test runs. var ttyArg = isTty ? "t" : ""; exitCode = CommandUtilities.ExecuteCommand("docker", $"exec -i{ttyArg} {container.ContainerId} /bin/bash {ContainerPaths.DockerTaskScriptPath} watchAndRun"); return(exitCode); }
public AuthenticatedImageUri(Lazy <string> imageUri) { _authenticatedUri = new Lazy <Task <AuthenticationResult> >(async() => { // TODO - Only run authentication if the image is not already available locally? That would eliminate excessive auth calls, // but could make it harder to spot when auth gets broken. var isAuthenticated = await Security.EnsureAuthenticatedWithEcr(); if (isAuthenticated && IsRemoteUri(imageUri.Value)) { Output.Info($"Pulling image {imageUri.Value}..."); await DockerCommands.PullImage(imageUri.Value); } return(isAuthenticated ? new AuthenticationResult(true, imageUri.Value) : new AuthenticationResult(false, imageUri.Value)); }); }
protected override async Task <int> Run(MarvelMicroserviceConfig config) { var exitCode = 0; var containerName = config.DevContainerName; var container = await DockerCommands.GetContainerByName(containerName); if (container?.IsRunning != true) { Output.Info($"Could not find running container {containerName}"); return(1); } Output.Info($"Attaching to container {containerName}"); exitCode = CommandUtilities.ExecuteCommand("docker", $"exec -i {container.ContainerId} /bin/bash {ContainerPaths.DockerTaskScriptPath} watchAndDebug"); return(exitCode); }
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); }
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); }