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);
        }
示例#2
0
        public static async Task <int> Build(MarvelMicroserviceConfig config, string publishedRuntimeImageName, BuildConfig buildConfig, IStaticAssetProcessor staticAssetProcessor)
        {
            var buildImageUri = await EcrResources.DotnetMicroserviceBuildImageUrl.EnsureImageIsPulled();

            if (!buildImageUri.WasSuccessful)
            {
                return(1);
            }

            var taskTimer = Stopwatch.StartNew();

            // Clear the old temp dir to ensure a fresh publish if running locally
            var outputPath = config.DotnetPublishOutputPath;

            if (Directory.Exists(outputPath))
            {
                Directory.Delete(outputPath, true);
            }
            Directory.CreateDirectory(outputPath);

            var dockerRunOptions = new List <string>
            {
                $"--rm",
                $"-e \"{LaunchCommand.ProjectNameEnvVariable}={config.ProjectName}\"",
                $"-e \"{LaunchCommand.SolutionNameEnvVariable}={config.SolutionName}\"",
                $"-e \"{WebappDirEnvVariable}={ContainerPaths.GetWebappDirectory(config)}\"",
                $"-e \"{BrowserAppDirEnvVariable}={ContainerPaths.GetBrowserAppDirectory(config)}\"",
                $"-v {config.BaseDirectory}:{ContainerPaths.MountedSourceDirectory}",
                $"-v {outputPath}:{ContainerPaths.BuildOutputDirectory}",
            };

            var runtimeImageLabelsTask = new LabelBuilder(config).GetLabels(buildConfig);

            var exitCode = 0;

            var dotnetBuildTimer = Stopwatch.StartNew();

            Output.Info($"Building dotnet webapp.");

            // Run container to build app and copy published resources to mounted output directory
            exitCode = CommandUtilities.ExecuteCommand("docker", $"run {string.Join(" ", dockerRunOptions)} {buildImageUri.Value} /bin/bash {ContainerPaths.DockerTaskScriptPath} buildWithoutCompose");
            if (exitCode != 0)
            {
                return(exitCode);
            }
            Output.Info($"dotnet webapp build completed {dotnetBuildTimer.Elapsed}");

            // Run static asset build from output directory
            await staticAssetProcessor.ProcessStaticAssets(Path.Combine(config.DotnetPublishOutputPath, "wwwroot"));

            // Build the image from the source output
            var dockerBuildTimer = Stopwatch.StartNew();

            Output.Info($"Building docker image {publishedRuntimeImageName}.");

            var labelArgs = LabelUtilities.FormatLabelsAsArguments(await runtimeImageLabelsTask);

            var buildArgs = labelArgs.Concat(new List <string> {
                $"-t {publishedRuntimeImageName}",
                $"--build-arg webappAssemblyPath={config.PublishedWebappAssemblyPath}",
                config.DotnetPublishOutputPath,
            });

            exitCode = CommandUtilities.ExecuteCommand("docker", $"build {string.Join(" ", buildArgs)}");
            if (exitCode != 0)
            {
                return(exitCode);
            }
            Output.Info($"Docker build completed {dockerBuildTimer.Elapsed}");

            Output.Info($"Build time elapsed {taskTimer.Elapsed}");

            return(exitCode);
        }