diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 15d9d221b0..221d8c17e5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -67,3 +67,14 @@ jobs: - name: dotnet test run: dotnet test working-directory: BotProject/CSharp + + docker-build: + name: Docker Build + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: docker-compose build + run: docker-compose build diff --git a/.gitignore b/.gitignore index b891ea6262..2c0aab8a74 100644 --- a/.gitignore +++ b/.gitignore @@ -397,3 +397,7 @@ MyBots/* # VsCode Composer/.vscode/ + +# Docker App Data +.appdata +docker-compose.override.yml diff --git a/BotProject/CSharp/Dockerfile b/BotProject/CSharp/Dockerfile index 8d0d33ba29..9893fa62bf 100644 --- a/BotProject/CSharp/Dockerfile +++ b/BotProject/CSharp/Dockerfile @@ -1,6 +1,6 @@ FROM mcr.microsoft.com/dotnet/core/sdk:2.1-alpine AS build -WORKDIR /app/botproject/csharp +WORKDIR /src/botproject/csharp COPY *.sln . COPY *.csproj . @@ -15,5 +15,6 @@ RUN dotnet publish -o out FROM mcr.microsoft.com/dotnet/core/aspnet:2.1-alpine AS runtime WORKDIR /app/botproject/csharp -COPY --from=build /app/botproject/csharp/out . -CMD ["dotnet", "BotProject.dll"] \ No newline at end of file +COPY --from=build /src/botproject/csharp/ComposerDialogs ./ComposerDialogs +COPY --from=build /src/botproject/csharp/out . +CMD ["dotnet", "BotProject.dll"] diff --git a/Composer/.dockerignore b/Composer/.dockerignore index c3066a3e4b..19245eab5f 100644 --- a/Composer/.dockerignore +++ b/Composer/.dockerignore @@ -7,4 +7,7 @@ **/server/tmp.zip # not ignore all lib folder because packages/lib, so probably we should rename that to libs packages/lib/*/lib -packages/extensions/*/lib \ No newline at end of file +packages/extensions/*/lib + +Dockerfile +.dockerignore diff --git a/Composer/Dockerfile b/Composer/Dockerfile index 5b25b5d824..351572a63c 100644 --- a/Composer/Dockerfile +++ b/Composer/Dockerfile @@ -1,39 +1,34 @@ -FROM node:12-alpine as build -WORKDIR /src/Composer - -# yes, docker copy is really this stupid, https://github.com/moby/moby/issues/15858 -COPY yarn.lock . -COPY .npmrc . -COPY package.json . -COPY packages/client/package.json ./packages/client/ -COPY yarn.lock ./packages/server/ -COPY packages/server/package.json ./packages/server/ -COPY packages/lib/package.json ./packages/lib/ -COPY packages/lib/code-editor/package.json ./packages/lib/code-editor/ -COPY packages/lib/shared/package.json ./packages/lib/shared/ -COPY packages/lib/indexers/package.json ./packages/lib/indexers/ -COPY packages/extensions/package.json ./packages/extensions/ -COPY packages/lib/eslint-plugin-bfcomposer/package.json ./packages/lib/eslint-plugin-bfcomposer/ -COPY packages/extensions/obiformeditor/package.json ./packages/extensions/obiformeditor/ -COPY packages/extensions/visual-designer/package.json ./packages/extensions/visual-designer/ +################# +# +# Because Composer is organized as a monorepo with multiple packages +# managed by yarn workspaces, our Dockerfile may not look like other +# node / react projects. Specifically, we have to add all source files +# before doing yarn install due to yarn workspace symlinking. +# +################ -# run yarn install as a distinct layer -RUN yarn install +FROM node:12-alpine as build +WORKDIR /src/Composer COPY . . +# run yarn install as a distinct layer +RUN yarn install --frozen-lock-file ENV NODE_OPTIONS "--max-old-space-size=4096" +ENV NODE_ENV "production" RUN yarn build:prod + # use a multi-stage build to reduce the final image size FROM node:12-alpine -WORKDIR /app/Composer/server -COPY --from=build /src/Composer/.npmrc . -COPY --from=build /src/Composer/packages/lib ./lib -COPY --from=build /src/Composer/packages/server . - -# update server package json to point to local packages -RUN node ./prepare-prod.js +WORKDIR /app/Composer +COPY --from=build /src/Composer/yarn.lock . +COPY --from=build /src/Composer/package.json . +COPY --from=build /src/Composer/packages/server ./packages/server +COPY --from=build /src/Composer/packages/lib ./packages/lib +COPY --from=build /src/Composer/packages/tools ./packages/tools -RUN yarn --production && yarn cache clean -CMD ["node", "build/server.js"] +ENV NODE_ENV "production" +RUN yarn --production --frozen-lockfile --force && yarn cache clean +WORKDIR /app/Composer +CMD ["yarn", "start:server"] diff --git a/Composer/packages/client/config/env.js b/Composer/packages/client/config/env.js index b64938b32a..6b8a48c3ad 100644 --- a/Composer/packages/client/config/env.js +++ b/Composer/packages/client/config/env.js @@ -42,7 +42,7 @@ dotenvFiles.forEach(dotenvFile => { function getGitSha() { try { - const sha = execSync('git rev-parse --short master'); + const sha = execSync('git rev-parse --short', { stdio: ['ignore', 'ignore', 'ignore'] }); return sha; } catch (e) { return 'test'; diff --git a/Composer/packages/server/prepare-prod.js b/Composer/packages/server/prepare-prod.js deleted file mode 100644 index 239530851b..0000000000 --- a/Composer/packages/server/prepare-prod.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * In a production environment (abs-h, docker), we need all dependencies to be co-located in the server node_modules. - * In development, yarn workspaces allows us to develop local packages simultaneously via symlinks, - * but on a production machine, we cannot use yarn workspaces and need to run `npm install --production` - * which is unable to resolve our local modules (i.e. @bfc/shared). - */ - -const fs = require('fs'); -const path = require('path'); - -const packageJsonPath = path.resolve(__dirname, 'package.json'); - -const localPackages = { - '@bfc/indexers': path.resolve(__dirname, './lib/indexers'), - '@bfc/shared': path.resolve(__dirname, './lib/shared'), -}; - -const packageJson = require(packageJsonPath); - -Object.keys(localPackages).forEach(p => { - packageJson.dependencies[p] = localPackages[p]; -}); - -fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); diff --git a/Composer/packages/server/src/settings/env.ts b/Composer/packages/server/src/settings/env.ts index fb7b623929..7414e92f98 100644 --- a/Composer/packages/server/src/settings/env.ts +++ b/Composer/packages/server/src/settings/env.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import path from 'path'; + export const absHosted = process.env.COMPOSER_AUTH_PROVIDER === 'abs-h'; export const absHostRoot = process.env.WEBSITE_HOSTNAME ? `https://${process.env.WEBSITE_HOSTNAME}` @@ -11,4 +13,7 @@ if (folder && folder.endsWith(':')) { folder = folder + '/'; } +export const environment = process.env.NODE_ENV || 'development'; export const botsFolder = folder; +export const botEndpoint = process.env.BOT_ENDPOINT || 'http://localhost:3979'; +export const appDataPath = process.env.COMPOSER_APP_DATA || path.resolve(__dirname, '../../data.json'); diff --git a/Composer/packages/server/src/settings/index.ts b/Composer/packages/server/src/settings/index.ts index 2b5db176c4..c68381dffa 100644 --- a/Composer/packages/server/src/settings/index.ts +++ b/Composer/packages/server/src/settings/index.ts @@ -1,15 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import os from 'os'; + import merge from 'lodash/merge'; import log from '../logger'; +import { Path } from '../utility/path'; -import settings from './settings'; - -// overall the guidance in settings.json is to list every item in "development" -// section with a default value, and override the value for different environment -// in later sections +import { botsFolder, botEndpoint, appDataPath, environment } from './env'; interface Settings { botAdminEndpoint: string; @@ -17,11 +16,22 @@ interface Settings { assetsLibray: string; runtimeFolder: string; botsFolder: string; + appDataPath: string; } -const defaultSettings = settings.development; -const environment = process.env.NODE_ENV || 'development'; -const environmentSettings = settings[environment]; +const envSettings: { [env: string]: Settings } = { + development: { + botAdminEndpoint: botEndpoint, + botEndpoint: 'http://localhost:3979', //botEndpoint, + assetsLibray: Path.resolve('./assets'), + runtimeFolder: Path.resolve('../../../BotProject/Templates'), + botsFolder: botsFolder || Path.join(os.homedir(), 'Documents', 'Composer'), + appDataPath, + }, +}; + +const defaultSettings = envSettings.development; +const environmentSettings = envSettings[environment]; const finalSettings = merge(defaultSettings, environmentSettings); diff --git a/Composer/packages/server/src/settings/settings.ts b/Composer/packages/server/src/settings/settings.ts deleted file mode 100644 index 618f8b110b..0000000000 --- a/Composer/packages/server/src/settings/settings.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import os from 'os'; - -import { Path } from '../utility/path'; - -import { botsFolder } from './env'; - -export default { - development: { - botAdminEndpoint: 'http://localhost:3979', - botEndpoint: 'http://localhost:3979', - assetsLibray: Path.resolve('./assets'), - runtimeFolder: Path.resolve('../../../BotProject/Templates'), - botsFolder: botsFolder || Path.join(os.homedir(), 'Documents', 'Composer'), - }, - container: { - botAdminEndpoint: 'http://botruntime:80', - }, -}; diff --git a/Composer/packages/server/src/store/store.ts b/Composer/packages/server/src/store/store.ts index ef071fafa1..4c4841e1fa 100644 --- a/Composer/packages/server/src/store/store.ts +++ b/Composer/packages/server/src/store/store.ts @@ -5,15 +5,15 @@ import fs from 'fs'; import path from 'path'; import log from '../logger'; +import settings from '../settings'; import localInitData from './data.template'; import abhInitData from './abh-template.json'; import { runMigrations } from './migrations'; + const isHostedInAzure = !!process.env.WEBSITE_NODE_DEFAULT_VERSION; const dataStorePath = - isHostedInAzure && process.env.HOME - ? path.resolve(process.env.HOME, './site/data.json') - : path.resolve(__dirname, '../../data.json'); + isHostedInAzure && process.env.HOME ? path.resolve(process.env.HOME, './site/data.json') : settings.appDataPath; let initData = isHostedInAzure ? abhInitData : localInitData; diff --git a/Composer/packages/tools/language-servers/language-generation/package.json b/Composer/packages/tools/language-servers/language-generation/package.json index a5679bc89b..317bdfa940 100644 --- a/Composer/packages/tools/language-servers/language-generation/package.json +++ b/Composer/packages/tools/language-servers/language-generation/package.json @@ -15,12 +15,12 @@ "lint:typecheck": "tsc --noEmit" }, "dependencies": { - "@types/node": "^12.0.4", "botbuilder-lg": "4.7.0-preview.93464", "request-light": "^0.2.2", "vscode-languageserver": "^5.3.0-next" }, "devDependencies": { + "@types/node": "^12.0.4", "express": "^4.17.1", "jest": "24.0.0", "rimraf": "^2.6.3", diff --git a/docker-compose.override.yml.example b/docker-compose.override.yml.example new file mode 100644 index 0000000000..61db49675f --- /dev/null +++ b/docker-compose.override.yml.example @@ -0,0 +1,12 @@ +# Use this to override Composer's default docker configuration + +version: "3" +services: + composer: + volumes: + - /Some/path/to/my/bots:/Bots + environment: + DEBUG: composer + botruntime: + volumes: + - /Some/path/to/my/bots:/Bots diff --git a/docker-compose.yml b/docker-compose.yml index 165a88b4f8..c28457b54c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,22 +3,19 @@ services: composer: build: Composer ports: - - "3000:5000" - - "5000:5000" - # expose 9229 for debugging - # - "9229:9229" + - "3000:3000" volumes: - - ./MyBots:/MyBots + - ~/Documents/Composer:/Bots - ./BotProject:/BotProject + - ./.appdata:/appdata environment: - - NODE_ENV=container - # command: - # - node - # - "--inspect-brk=0.0.0.0" - # - "build/server.js" + COMPOSER_BOTS_FOLDER: /Bots + COMPOSER_APP_DATA: /appdata/data.json + BOT_ENDPOINT: http://botruntime:80 + PORT: 3000 botruntime: build: BotProject/CSharp ports: - "3979:80" volumes: - - ./MyBots:/app/MyBots + - ~/Documents/Composer:/Bots