rlin
rlin

Reputation: 13

Azure Function in Docker Container runs in Visual Studio, but does nothing from command line

My goal is to have a timer azure function (leveraging Selenium Chromedriver) run inside a docker container. it works in Visual Studio when i click this VS Docker Button; however, when i execute the same docker command from VS in the command line, nothing happens. here's the command VS executes

docker run -dt -v "C:\Users\rlin\vsdbg\vs2017u5:/remote_debugger:rw" -e "ASPNETCORE_ENVIRONMENT=Development" -p 34480:80 --name Retriever --entrypoint tail retriever -f /dev/null

here's my dockerfile for your reference

#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/azure-functions/dotnet:3.0 AS base
WORKDIR /home/site/wwwroot
EXPOSE 80

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /home/site/wwwroot
COPY . Retriever/
WORKDIR /home/site/wwwroot/Retriever
RUN dotnet build -c Release -o /home/site/wwwroot

FROM build AS publish
RUN dotnet publish -c Release -o /home/site/wwwroot

FROM base AS final
WORKDIR /home/site/wwwroot
COPY --from=publish /home/site/wwwroot .
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
    AzureFunctionsJobHost__Logging__Console__IsEnabled=true \
    AzureWebJobsStorage="StorageConnectionString"
    URL="url" \
    USERNAME="username" \
    PASSWORD="password" \
    SFTP="sftp" \
    CONTAINER="container"

WORKDIR /home/site/wwwroot
RUN apt-get update && \
    apt-get install -y gnupg wget curl unzip --no-install-recommends && \
    wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \
    echo "deb http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list && \
    apt-get update -y && \
    apt-get install -y google-chrome-stable && \
    CHROMEVER=$(google-chrome --product-version | grep -o "[^\.]*\.[^\.]*\.[^\.]*") && \
    DRIVERVER=$(curl -s "https://chromedriver.storage.googleapis.com/LATEST_RELEASE_$CHROMEVER") && \
    wget -q --continue -P /chromedriver "http://chromedriver.storage.googleapis.com/$DRIVERVER/chromedriver_linux64.zip" && \
    unzip /chromedriver/chromedriver* -d /chromedriver

WORKDIR /
CMD ["/home/site/wwwroot/bin/Retriever.dll"]

been stuck on this for days, so any help is much appreciated.

EDIT: here's the code for ref

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using OpenQA.Selenium.Chrome;
using Microsoft.Extensions.Configuration;
using OpenQA.Selenium;
using System.Collections.Generic;
using Azure.Storage.Blobs;
using System.IO.Compression;

namespace Retriever
{
    public class Retriever
    {
        private IConfigurationRoot config;

        [FunctionName("Retriever")]
        public void Run(
            [TimerTrigger("0 */1 * * * *", RunOnStartup = true)] TimerInfo myTimer,
            //[HttpTrigger(AuthorizationLevel.System, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            config = new ConfigurationBuilder()
                .SetBasePath(Environment.CurrentDirectory)
                .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables()
                .Build();

            Retrieve(log);
            log.LogInformation("Uploaded attachments");
            //return new OkResult();
        }
        public void Retrieve(ILogger log)
        {
            log.LogInformation("Retrieving attachments");

            ChromeOptions options = new ChromeOptions();
            options.AddArgument("--headless");
            options.AddArgument("--no-sandbox"); // Bypass OS security model
            options.AddArgument("start-maximized"); // open Browser in maximized mode
            options.AddArgument("disable-infobars"); // disabling infobars
            options.AddArgument("--disable-extensions"); // disabling extensions
            options.AddArgument("--disable-gpu"); // applicable to windows os only
            options.AddArgument("--disable-dev-shm-usage"); // overcome limited resource problems
            options.AddArgument("--whitelisted-ips");
            options.AddArgument("--proxy-server=direct://");
            options.AddArgument("--proxy-bypass-list=*");
            options.BinaryLocation = "/opt/google/chrome/google-chrome"; //Google Chrome path
            options.AddUserProfilePreference("download.prompt_for_download", false); // DO NOT prompt for download
            options.AddUserProfilePreference("download.default_directory", Path.GetTempPath()); // saves to Temp directory

            ChromeDriverService service = ChromeDriverService.CreateDefaultService("/chromedriver/", "chromedriver"); // use "/usr/bin/" and "chromedriver" in deployment

            ChromeDriver driver = new ChromeDriver(service, options, TimeSpan.FromMinutes(3));
            driver.Manage().Timeouts().ImplicitWait = new TimeSpan(5, 0, 0);

            // Login and Download Steps //
            driver.Navigate().GoToUrl(config.GetValue<string>("URL"));

            IWebElement username = driver.FindElement(By.XPath("//*[@id=\"username\"]"));
            username.SendKeys(config.GetValue<string>("USERNAME"));

            IWebElement next = driver.FindElement(By.CssSelector("body > mc-login > div > div.container > div > div > div.panel.panel-default.panel-shadow.login-panel > div > div > div > form > button"));
            next.Click();

            IWebElement password = driver.FindElement(By.CssSelector("#password"));
            password.SendKeys(config.GetValue<string>("PASSWORD"));

            IWebElement login = driver.FindElement(By.CssSelector("body > mc-login > div > div.container > div > div > div.panel.panel-default.panel-shadow.login-panel > div > div > div > form > button"));
            login.Click();

            IWebElement inbox = driver.FindElement(By.LinkText("Inbox"));
            inbox.Click();
            
            ICollection<IWebElement> emails = driver.FindElements(By.CssSelector("tr"));
            System.Threading.Thread.Sleep(new TimeSpan(0, 0, 2));
            foreach(IWebElement email in emails)
            {
                if (email.Displayed)
                {
                    email.Click();

                    IWebElement view = driver.FindElement(By.LinkText("View"));
                    view.Click();

                    string filename = driver.FindElement(By.CssSelector("body > div.page-container.with-sidebar.full-height.snap-content > div.main-content.full-height > div.ng-isolate-scope > div > div > div.container-fluid.full-height.fill-container.mainarea.ng-scope.mc-tab-unique-main > mc-list-detail > div > div.col-xs-12.col-sm-7.col-md-8.col-lg-8.hidden-xs.full-height.animate-active.no-padding > div > div.dynamic-full-height.horizontal-scroll.vertical-scroll.panel-half-margin-top-negative.ng-scope > div.pull-left.full-width.ng-scope > detail > div:nth-child(4) > div > mc-thumbnail > div > div:nth-child(2) > ul > li > span > span:nth-child(2)")).GetAttribute("innerHTML");
                    string filepath = Path.Combine(Path.GetTempPath(), filename);

                    IWebElement download = driver.FindElement(By.LinkText("Download"));
                    download.Click();
                    System.Threading.Thread.Sleep(new TimeSpan(0, 0, 2));
                    UploadToBlobStorage(log, filepath, filename);
                }
            }
            driver.Close();
            driver.Quit();
            driver.Dispose();
            service.Dispose();
        }
        public void UploadToBlobStorage(ILogger log, string filepath, string filename)
        {
            BlobServiceClient serviceClient = new BlobServiceClient(config.GetValue<string>("SFTP"));
            BlobContainerClient containerClient = serviceClient.GetBlobContainerClient(config.GetValue<string>("CONTAINER"));
            BlobClient client = containerClient.GetBlobClient("uploads/" + filename + ".txt");

            ZipArchive z = ZipFile.Open(filepath, ZipArchiveMode.Update);
            foreach(ZipArchiveEntry e in z.Entries)
            {
                client.Upload(e.Open(), overwrite: true);
            }
            z.Dispose();
            if(File.Exists(filepath)) { File.Delete(filepath); }
        }
    }
}

Upvotes: 1

Views: 2169

Answers (1)

H. Dennhardt
H. Dennhardt

Reputation: 136

The command you posted is only used for Visual Studios debugging functionality. It overwites the entrypoint with tail -f /dev/null which is basically "loop forever".

You will need to run docker build <path to dir where Dockerfile is located> -t retriever to build the image and then docker run -p 80:80 retriever to run it. This should make the app available on port 80 of your system. If you want the app on another port, you can use -p <another port>:80

The last line (CMD ["/home/site/wwwroot/bin/Retriever.dll"]) in your Dockerfile overwrites the preset CMD of azure-functions/dotnet (/azure-functions-host/Microsoft.Azure.WebJobs.Script.WebHost) and will lead to the error "exec: \"dotnet\": executable file not found in $PATH". Your function is supposed to be run by WebHost, not by invoking the dll directly. Remove this line. The one above it is also unnecessary.

Your Dockerfile also has another issue (it will possibly still work without adressing them): You are copying your sources, building and publishing all to the same directory /home/site/wwwroot. I would suggest changing it like this for the build stage:

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src
COPY . .
RUN dotnet build -c Release -o /app/build

For the publish stage:

FROM build AS publish
RUN dotnet publish -c Release -o /app/publish

And for final:

FROM base AS final
WORKDIR /home/site/wwwroot
COPY --from=publish /app/publish .
[...]

Upvotes: 1

Related Questions