diff --git a/.github/workflows/build-push-docker.yml b/.github/workflows/build-push-docker.yml index 5eae37a..d13bd3a 100644 --- a/.github/workflows/build-push-docker.yml +++ b/.github/workflows/build-push-docker.yml @@ -22,6 +22,9 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 + - name: Download binaries + run: bash downloadBinaries.sh + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 485cec6..6d879a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.5.3] — 2024-01-11 + +### Changed + +- Fixed error on Windows + ## [0.5.2] — 2023-12-06 ### Changed diff --git a/Dockerfile b/Dockerfile index 8bda6b7..a43bc74 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17-alpine AS builder +FROM golang:1.21-alpine AS builder ENV CGO_ENABLED=0 WORKDIR /backend COPY vm/go.* . @@ -55,6 +55,7 @@ LABEL org.opencontainers.image.title="LocalStack" \ com.docker.extension.changelog="https://github.com/localstack/localstack-docker-extension/blob/main/CHANGELOG.md" COPY --from=builder /backend/bin/service / + COPY docker-compose.yaml . COPY metadata.json . COPY localstack.svg . @@ -63,5 +64,10 @@ COPY --chmod=0755 scripts/windows/checkWSLOS.cmd /windows/checkWSLOS.cmd COPY --chmod=0755 scripts/windows/checkUser.cmd /windows/checkUser.cmd COPY --chmod=0755 scripts/darwin/checkUser.sh /darwin/checkUser.sh COPY --chmod=0755 scripts/linux/checkUser.sh /linux/checkUser.sh +COPY --chmod=0755 binaries/windows/localstack-windows-amd.exe /windows/localstack-windows-amd.exe +COPY --chmod=0755 binaries/darwin/localstack-darwin-amd /darwin/localstack-darwin-amd +COPY --chmod=0755 binaries/darwin/localstack-darwin-arm /darwin/localstack-darwin-arm +COPY --chmod=0755 binaries/linux/localstack-linux-arm /linux/localstack-linux-arm +COPY --chmod=0755 binaries/linux/localstack-linux-amd /linux/localstack-linux-amd CMD /service -socket /run/guest-services/extension-LocalStack.sock diff --git a/Makefile b/Makefile index ec46674..3120f24 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ IMAGE?=localstack/localstack-docker-desktop -TAG?=0.5.2 +TAG?=0.5.3 BUILDER=buildx-multi-arch @@ -7,7 +7,8 @@ INFO_COLOR = \033[0;36m NO_COLOR = \033[m build-extension: ## Build service image to be deployed as a desktop extension - docker build --tag=$(IMAGE):$(TAG) . + ls binaries/linux/localstack-* > /dev/null 2>&1 || ./downloadBinaries.sh + docker build --tag=$(IMAGE):$(TAG) . install-extension: build-extension ## Install the extension docker extension install $(IMAGE):$(TAG) diff --git a/binaries/.gitkeep b/binaries/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/downloadBinaries.sh b/downloadBinaries.sh new file mode 100755 index 0000000..b86da5b --- /dev/null +++ b/downloadBinaries.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +latest_version=$(curl -s https://api.github.com/repos/localstack/localstack-cli/releases/latest | grep "tag_name" | cut -d'"' -f4) +version_number=$(echo "$latest_version" | grep -oP 'v(\d+\.\d+\.\d+)' | sed 's/v//') + +wget "https://github.com/localstack/localstack-cli/releases/latest/download/localstack-cli-${version_number}-linux-amd64-onefile.tar.gz" -O localstack-cli-linux-amd64-onefile.tar.gz +wget "https://github.com/localstack/localstack-cli/releases/latest/download/localstack-cli-${version_number}-linux-arm64-onefile.tar.gz" -O localstack-cli-linux-arm64-onefile.tar.gz +wget "https://github.com/localstack/localstack-cli/releases/latest/download/localstack-cli-${version_number}-darwin-amd64-onefile.tar.gz" -O localstack-cli-darwin-amd64-onefile.tar.gz +wget "https://github.com/localstack/localstack-cli/releases/latest/download/localstack-cli-${version_number}-darwin-arm64-onefile.tar.gz" -O localstack-cli-darwin-arm64-onefile.tar.gz +wget "https://github.com/localstack/localstack-cli/releases/latest/download/localstack-cli-${version_number}-windows-amd64-onefile.zip" -O localstack-cli-windows-amd64-onefile.zip + +mkdir ./binaries/linux +tar -xzvf localstack-cli-linux-amd64-onefile.tar.gz -C ./binaries/linux && mv ./binaries/linux/localstack ./binaries/linux/localstack-linux-amd && rm localstack-cli-linux-amd64-onefile.tar.gz +tar -xzvf localstack-cli-linux-arm64-onefile.tar.gz -C ./binaries/linux && mv ./binaries/linux/localstack ./binaries/linux/localstack-linux-arm && rm localstack-cli-linux-arm64-onefile.tar.gz + +mkdir ./binaries/darwin +tar -xzvf localstack-cli-darwin-amd64-onefile.tar.gz -C ./binaries/darwin && mv ./binaries/darwin/localstack ./binaries/darwin/localstack-darwin-amd && rm localstack-cli-darwin-amd64-onefile.tar.gz +tar -xzvf localstack-cli-darwin-arm64-onefile.tar.gz -C ./binaries/darwin && mv ./binaries/darwin/localstack ./binaries/darwin/localstack-darwin-arm && rm localstack-cli-darwin-arm64-onefile.tar.gz + +mkdir ./binaries/windows +unzip localstack-cli-windows-amd64-onefile.zip -d ./binaries/windows && mv ./binaries/windows/localstack.exe ./binaries/windows/localstack-windows-amd.exe && rm localstack-cli-windows-amd64-onefile.zip + diff --git a/metadata.json b/metadata.json index c6d42b5..6ddb230 100644 --- a/metadata.json +++ b/metadata.json @@ -22,11 +22,23 @@ "darwin": [ { "path": "/darwin/checkUser.sh" + }, + { + "path": "/darwin/localstack-darwin-amd" + }, + { + "path": "/darwin/localstack-darwin-arm" } ], "linux": [ { "path": "/linux/checkUser.sh" + }, + { + "path": "/linux/localstack-linux-amd" + }, + { + "path": "/linux/localstack-linux-arm" } ], "windows": [ @@ -35,6 +47,9 @@ }, { "path": "/windows/checkUser.cmd" + }, + { + "path": "/windows/localstack-windows-amd.exe" } ] } diff --git a/ui/src/components/Feedback/DownloadProgress/DownloadProgress.tsx b/ui/src/components/Feedback/DownloadProgress/DownloadProgress.tsx index 5dde322..f4ce336 100644 --- a/ui/src/components/Feedback/DownloadProgress/DownloadProgress.tsx +++ b/ui/src/components/Feedback/DownloadProgress/DownloadProgress.tsx @@ -25,7 +25,7 @@ interface DownloadProgressProps { export const DownloadProgress = ({ callback, imageName }: DownloadProgressProps): ReactElement => { - const ddClient = useDDClient(); + const { client: ddClient } = useDDClient(); const [statusMap, setStatusMap] = useState>(new Map()); const [isDone, setIsDone] = useState(false); const percentage = Array.from(statusMap.entries()) diff --git a/ui/src/components/Header/Controller.tsx b/ui/src/components/Header/Controller.tsx index 2c4131c..09afea4 100644 --- a/ui/src/components/Header/Controller.tsx +++ b/ui/src/components/Header/Controller.tsx @@ -11,15 +11,14 @@ import { } from '../../services'; import { DEFAULT_CONFIGURATION_ID, - FLAGS, PRO_IMAGE, COMMUNITY_IMAGE, + FLAGS_AS_STRING, } from '../../constants'; import { LongMenu } from './Menu'; import { DockerContainer, DockerImage } from '../../types'; import { DownloadProgressDialog } from '../Feedback/DownloadProgressDialog'; import { ProgressButton } from '../Feedback'; -import { generateCLIArgs } from '../../services/util/cli'; const EXCLUDED_ERROR_TOAST = ['INFO', 'WARN', 'DEBUG']; @@ -31,7 +30,7 @@ export const Controller = (): ReactElement => { const [downloadProps, setDownloadProps] = useState({ open: false, image: COMMUNITY_IMAGE }); const [isStarting, setIsStarting] = useState(false); const [isStopping, setIsStopping] = useState(false); - const ddClient = useDDClient(); + const { client: ddClient, getBinary } = useDDClient(); const isRunning = data && data.State === 'running'; const isUnhealthy = data && data.Status.includes('unhealthy'); const tooltipLabel = isUnhealthy ? 'Unhealthy' : 'Healthy'; @@ -48,52 +47,44 @@ export const Controller = (): ReactElement => { } }, [isLoading]); - const buildHostArgs = () => { - let location = 'LOCALSTACK_VOLUME_DIR=/tmp/localstack/volume'; - let homeDir = `HOME=/home/${user}`; + const buildHostArgs = (): NodeJS.ProcessEnv => { + let location = '/tmp/localstack/volume'; if (!hasSkippedConfiguration) { switch (ddClient.host.platform) { case 'win32': - location = `LOCALSTACK_VOLUME_DIR=\\\\wsl$\\${os}\\home\\${user}\\.cache\\localstack\\volume`; - homeDir = `HOME=\\\\wsl$\\${os}\\home\\${user}`; + location = `\\\\wsl$\\${os}\\home\\${user}\\.cache\\localstack\\volume`; break; case 'darwin': - location = `LOCALSTACK_VOLUME_DIR=/Users/${user}/Library/Caches/localstack/volume`; - homeDir = `HOME=/Users/${user}`; + location = `/Users/${user}/Library/Caches/localstack/volume`; break; default: - location = `LOCALSTACK_VOLUME_DIR=/home/${user}/.cache/localstack/volume`; - homeDir = `HOME=/home/${user}`; + location = `/home/${user}/.cache/localstack/volume`; } } - return ['-e', location, '-e', homeDir]; + return { LOCALSTACK_VOLUME_DIR: location }; }; - const normalizeArguments = async () => { - const extendedFlag = FLAGS.map(x => x); // clone - let isPro = false; + const normalizeArguments = (): NodeJS.ProcessEnv => { const addedArgs = configData.configs.find(config => config.id === runningConfig) .vars.map(item => { if (item.variable === 'DOCKER_FLAGS') { - extendedFlag[1] = FLAGS.at(1).slice(0, -1).concat(` ${item.value}'`); - } - if (item.variable === 'LOCALSTACK_AUTH_TOKEN') { - isPro = true; + return { [item.variable]: `${FLAGS_AS_STRING} ${item.value}` }; } - return ['-e', `${item.variable}=${item.value}`]; - }).flat(); + return { [item.variable]: item.value }; + }); - return [ - ...extendedFlag, - ...buildHostArgs(), - ...addedArgs, - ...generateCLIArgs({ call: 'start', pro: isPro }), - ]; + return [...addedArgs, buildHostArgs()].reduce((acc, obj) => { + const [key, value] = Object.entries(obj)[0]; + acc[key] = value; + return acc; + }, {} as NodeJS.ProcessEnv); }; const start = async () => { + setIsStarting(true); + const images = await ddClient.docker.listImages() as [DockerImage]; const isPro = configData.configs.find(config => config.id === runningConfig) @@ -112,10 +103,16 @@ export const Controller = (): ReactElement => { return; } - const args = await normalizeArguments(); + const args = normalizeArguments(); + + const binary = getBinary(); + if (!binary) { + setIsStarting(false); + return; + } - setIsStarting(true); - ddClient.docker.cli.exec('run', args, { + ddClient.extension.host?.cli.exec(binary, ['start', '--no-banner', '-d'], { + env: args, stream: { onOutput(data): void { const shouldDisplayError = !EXCLUDED_ERROR_TOAST.some(item => data.stderr?.includes(item)) && data.stderr; diff --git a/ui/src/components/Header/Header.tsx b/ui/src/components/Header/Header.tsx index f727426..6e8c86d 100644 --- a/ui/src/components/Header/Header.tsx +++ b/ui/src/components/Header/Header.tsx @@ -5,7 +5,7 @@ import { useDDClient } from '../../services'; import { Controller } from './Controller'; export const Header = (): ReactElement => { - const ddClient = useDDClient(); + const { client: ddClient } = useDDClient(); return ( { const [anchorEl, setAnchorEl] = useState(null); const [openModal, setOpenModal] = useState(false); const [images, setImages] = useState(['Loading...']); - const ddClient = useDDClient(); + const { client: ddClient } = useDDClient(); const open = Boolean(anchorEl); diff --git a/ui/src/components/Views/Configs/SettingsForm.tsx b/ui/src/components/Views/Configs/SettingsForm.tsx index 948d0a5..47f756b 100644 --- a/ui/src/components/Views/Configs/SettingsForm.tsx +++ b/ui/src/components/Views/Configs/SettingsForm.tsx @@ -39,7 +39,7 @@ export const SettingsForm = ({ initialState }: MountPointFormProps): ReactElemen const [activeStep, setActiveStep] = useState(initialState); const { setMountPointData, user, os } = useMountPoint(); - const ddClient = useDDClient(); + const { client: ddClient } = useDDClient(); const steps = ['Enable Docker Desktop option', 'Launching pro container', 'Set mount point']; diff --git a/ui/src/components/Views/Logs/LogsPage.tsx b/ui/src/components/Views/Logs/LogsPage.tsx index 939d7bd..b45982c 100644 --- a/ui/src/components/Views/Logs/LogsPage.tsx +++ b/ui/src/components/Views/Logs/LogsPage.tsx @@ -4,7 +4,7 @@ import { useDDClient, useLocalStack } from '../../../services'; export const LogsPage = (): ReactElement => { const [logs, setLogs] = useState([]); - const ddClient = useDDClient(); + const { client: ddClient } = useDDClient(); const { data } = useLocalStack(); useEffect(() => { diff --git a/ui/src/components/Views/Update/UpdateDialog.tsx b/ui/src/components/Views/Update/UpdateDialog.tsx index febeb4d..054c7c2 100644 --- a/ui/src/components/Views/Update/UpdateDialog.tsx +++ b/ui/src/components/Views/Update/UpdateDialog.tsx @@ -7,7 +7,6 @@ import { } from '@mui/material'; import React, { ReactElement, useEffect, useState } from 'react'; import { useDDClient } from '../../../services'; -import { generateCLIArgs } from '../../../services/util/cli'; type Props = { open: boolean, @@ -16,11 +15,15 @@ type Props = { export const UpdateDialog = ({ open, onClose }: Props): ReactElement => { const [logs, setLogs] = useState([]); - const ddClient = useDDClient(); + const { client: ddClient, getBinary } = useDDClient(); const [isUpdating, setIsUpdating] = useState(true); useEffect(() => { - const listener = ddClient.docker.cli.exec('run', generateCLIArgs({ call: 'update' }), { + const binary = getBinary(); + if (!binary) { + return; + } + const listener = ddClient.extension.host?.cli.exec(binary, ['update', 'docker-images'], { stream: { onOutput(data): void { let resultStr = data.stdout @@ -28,7 +31,7 @@ export const UpdateDialog = ({ open, onClose }: Props): ReactElement => { .replaceAll('✔', '✅') .replaceAll('✖', '❌'); - if (data.stdout.includes('Updating docker images')) { + if (resultStr.includes('Updating docker images')) { resultStr = 'Updating Docker images'; } diff --git a/ui/src/constants/docker.ts b/ui/src/constants/docker.ts index 38ed615..f1b23ac 100644 --- a/ui/src/constants/docker.ts +++ b/ui/src/constants/docker.ts @@ -1,36 +1,2 @@ -import { PRO_IMAGE, COMMUNITY_IMAGE } from './common'; - -export const COMMON_ARGS = [ - '--label', - 'cloud.localstack.spawner=true', - '--rm', - '-i', - '--entrypoint=', - '-v', - '/var/run/docker.sock:/var/run/docker.sock', -]; - -export const PRO_CLI = [ - PRO_IMAGE, - './.venv/bin/python3', - '-m', - 'localstack.cli.main', -]; - -export const COMMUNITY_CLI = [COMMUNITY_IMAGE, 'bin/localstack']; - -export const START_ARGS = [ - 'start', - '-d', -]; - -export const UPDATE_ARGS = [ - 'update', - 'docker-images', -]; - -export const FLAGS = [ - '-e', - // eslint-disable-next-line max-len - 'DOCKER_FLAGS=--label com.docker.compose.project=localstack_localstack-docker-desktop-desktop-extension --label com.docker.desktop.extension=true --label com.docker.compose.project.config_files', -]; +// eslint-disable-next-line max-len +export const FLAGS_AS_STRING = '--label com.docker.compose.project=localstack_localstack-docker-desktop-desktop-extension --label com.docker.desktop.extension=true --label com.docker.compose.project.config_files'; diff --git a/ui/src/services/hooks/api.ts b/ui/src/services/hooks/api.ts index 784ecd5..01aa9cc 100644 --- a/ui/src/services/hooks/api.ts +++ b/ui/src/services/hooks/api.ts @@ -30,7 +30,7 @@ const adaptVersionData = (data: HTTPMessageBody, error: Error) => { export const useRunConfigs = (): useRunConfigsReturn => { const cacheKey = STORAGE_KEY_ENVVARS; - const ddClient = useDDClient(); + const { client: ddClient } = useDDClient(); const { data, mutate, isValidating, error } = useSWR( cacheKey, () => (ddClient.extension.vm.service.get('/configs') as Promise), @@ -78,7 +78,7 @@ interface useMountPointReturn { } export const useMountPoint = (): useMountPointReturn => { - const ddClient = useDDClient(); + const { client: ddClient } = useDDClient(); const cacheKey = STORAGE_KEY_MOUNT; const { data, mutate, isValidating, error } = useSWR( @@ -111,7 +111,7 @@ interface useLocalStackReturn { } export const useLocalStack = (): useLocalStackReturn => { - const ddClient = useDDClient(); + const { client: ddClient } = useDDClient(); const cacheKey = STORAGE_KEY_LOCALSTACK; const { data, mutate } = useSWR( diff --git a/ui/src/services/hooks/utils.ts b/ui/src/services/hooks/utils.ts index 79b2e25..cc22d13 100644 --- a/ui/src/services/hooks/utils.ts +++ b/ui/src/services/hooks/utils.ts @@ -2,7 +2,44 @@ import { DockerDesktopClient } from '@docker/extension-api-client-types/dist/v1' import { useContext } from 'react'; import { GlobalDDContext } from '../context/GlobalDDContext'; -export const useDDClient = (): DockerDesktopClient => { + +export interface useDDClientReturn { + client: DockerDesktopClient, + getBinary: () => string, +} + + +export const useDDClient = (): useDDClientReturn => { const { client } = useContext(GlobalDDContext); - return client; + const getBinary = () => { + let architecture = ''; + if (client.host.arch === 'x64') { + architecture = 'amd'; + } else if (client.host.arch === 'arm64') { + architecture = 'arm'; + } else { + client.desktopUI.toast.error(`Extension does not support ${client.host.arch} architecture`); + return null; + } + + let os = ''; + if (client.host.platform === 'darwin' || client.host.platform === 'linux') { + os = client.host.platform; + } else if (client.host.platform === 'win32' && architecture === 'amd') { + os = 'windows'; + } else { + client.desktopUI.toast.error( + `Extension does not support ${client.host.platform} operating system platform (${architecture})`, + ); + return null; + } + + const fullName = `localstack-${os}-${architecture}${os === 'windows' ? '.exe' : ''}`; + return fullName; + }; + + return { + client, + getBinary, + }; }; diff --git a/ui/src/services/util/cli.ts b/ui/src/services/util/cli.ts deleted file mode 100644 index 344684c..0000000 --- a/ui/src/services/util/cli.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { COMMON_ARGS, COMMUNITY_CLI, PRO_CLI, START_ARGS, UPDATE_ARGS } from '../../constants'; - -export interface generateCLIArgsProps { - call: 'start' | 'update', - pro?: boolean, -} - -export const generateCLIArgs = ({ call, pro = false }: generateCLIArgsProps): string[] => { - const callArgs = [...COMMON_ARGS]; - if (pro) { - callArgs.push(...PRO_CLI); - } else { - callArgs.push(...COMMUNITY_CLI); - } - - switch (call) { - case 'start': callArgs.push(...START_ARGS); - break; - default: callArgs.push(...UPDATE_ARGS); - break; - } - return callArgs; - -};