Repository: loft-sh/devpod Branch: main Commit: 5a0efcbff661 Files: 13349 Total size: 104.8 MB Directory structure: gitextract_ynafywdj/ ├── .devcontainer/ │ ├── devcontainer.json │ └── post_create.sh ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.md │ │ └── feature-request.md │ ├── devcontainer/ │ │ ├── Dockerfile │ │ └── devcontainer.json │ ├── licenses.tmpl │ └── workflows/ │ ├── build-devcontainer-image.yaml │ ├── e2e-tests.yaml │ ├── e2e-win-full-tests.yaml │ ├── go-licenses-check.yaml │ ├── go-licenses.yaml │ ├── golangci-lint.yaml │ ├── release.yaml │ ├── stale.yaml │ ├── ui-ci.yaml │ └── unit-tests.yaml ├── .gitignore ├── .golangci.yaml ├── .vscode/ │ ├── launch.json │ └── settings.json ├── COMMUNITY.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── cmd/ │ ├── agent/ │ │ ├── agent.go │ │ ├── container/ │ │ │ ├── container.go │ │ │ ├── credentials_server.go │ │ │ ├── daemon.go │ │ │ ├── openvscode_async.go │ │ │ ├── setup.go │ │ │ ├── setup_loft_platform_access.go │ │ │ ├── setup_windows.go │ │ │ ├── ssh_server.go │ │ │ └── vscode_async.go │ │ ├── container_tunnel.go │ │ ├── daemon.go │ │ ├── docker_credentials.go │ │ ├── git_credentials.go │ │ ├── git_ssh_signature.go │ │ ├── git_ssh_signature_helper.go │ │ └── workspace/ │ │ ├── build.go │ │ ├── delete.go │ │ ├── install_dotfiles.go │ │ ├── logs.go │ │ ├── logs_daemon.go │ │ ├── setup_gpg.go │ │ ├── status.go │ │ ├── stop.go │ │ ├── up.go │ │ ├── update_config.go │ │ └── workspace.go │ ├── build.go │ ├── completion/ │ │ └── suggestions.go │ ├── context/ │ │ ├── context.go │ │ ├── create.go │ │ ├── delete.go │ │ ├── list.go │ │ ├── options.go │ │ ├── set_options.go │ │ └── use.go │ ├── delete.go │ ├── export.go │ ├── flags/ │ │ └── flags.go │ ├── helper/ │ │ ├── check_provider_update.go │ │ ├── docker_credentials.go │ │ ├── fleet_helper.go │ │ ├── get_image.go │ │ ├── get_provider_name.go │ │ ├── get_workspace_config.go │ │ ├── get_workspace_name.go │ │ ├── get_workspace_uid.go │ │ ├── helper.go │ │ ├── http/ │ │ │ ├── http.go │ │ │ └── request.go │ │ ├── json/ │ │ │ ├── get.go │ │ │ └── json.go │ │ ├── sh.go │ │ ├── ssh_client.go │ │ ├── ssh_git_clone.go │ │ ├── ssh_server.go │ │ └── strings/ │ │ └── strings.go │ ├── ide/ │ │ ├── ide.go │ │ ├── list.go │ │ ├── options.go │ │ ├── set_options.go │ │ └── use.go │ ├── import.go │ ├── list.go │ ├── logs.go │ ├── logs_daemon.go │ ├── machine/ │ │ ├── create.go │ │ ├── delete.go │ │ ├── inspect.go │ │ ├── list.go │ │ ├── machine.go │ │ ├── ssh.go │ │ ├── start.go │ │ ├── status.go │ │ └── stop.go │ ├── ping.go │ ├── pro/ │ │ ├── add/ │ │ │ ├── add.go │ │ │ └── cluster.go │ │ ├── check_health.go │ │ ├── check_update.go │ │ ├── completion/ │ │ │ └── suggestions.go │ │ ├── create_workspace.go │ │ ├── daemon/ │ │ │ ├── daemon.go │ │ │ ├── netcheck.go │ │ │ ├── start.go │ │ │ └── status.go │ │ ├── delete.go │ │ ├── flags/ │ │ │ └── flags.go │ │ ├── import_workspace.go │ │ ├── list.go │ │ ├── list_clusters.go │ │ ├── list_projects.go │ │ ├── list_templates.go │ │ ├── list_workspaces.go │ │ ├── login.go │ │ ├── pro.go │ │ ├── provider/ │ │ │ ├── create/ │ │ │ │ ├── create.go │ │ │ │ └── workspace.go │ │ │ ├── delete.go │ │ │ ├── get/ │ │ │ │ ├── get.go │ │ │ │ ├── self.go │ │ │ │ ├── version.go │ │ │ │ └── workspace.go │ │ │ ├── health.go │ │ │ ├── list/ │ │ │ │ ├── clusters.go │ │ │ │ ├── list.go │ │ │ │ ├── projects.go │ │ │ │ ├── templates.go │ │ │ │ └── workspaces.go │ │ │ ├── provider.go │ │ │ ├── rebuild.go │ │ │ ├── ssh.go │ │ │ ├── status.go │ │ │ ├── stop.go │ │ │ ├── up.go │ │ │ ├── update/ │ │ │ │ ├── update.go │ │ │ │ └── workspace.go │ │ │ └── watch/ │ │ │ ├── watch.go │ │ │ └── workspaces.go │ │ ├── rebuild.go │ │ ├── reset/ │ │ │ ├── password.go │ │ │ └── reset.go │ │ ├── self.go │ │ ├── sleep.go │ │ ├── start.go │ │ ├── update_provider.go │ │ ├── update_workspace.go │ │ ├── version.go │ │ ├── wakeup.go │ │ └── watch_workspaces.go │ ├── profile.go │ ├── provider/ │ │ ├── add.go │ │ ├── delete.go │ │ ├── list-default-providers.go │ │ ├── list.go │ │ ├── options.go │ │ ├── provider.go │ │ ├── set_options.go │ │ ├── update.go │ │ └── use.go │ ├── root.go │ ├── ssh.go │ ├── status.go │ ├── stop.go │ ├── troubleshoot.go │ ├── up.go │ ├── upgrade.go │ ├── use/ │ │ └── use.go │ └── version.go ├── community.yaml ├── desktop/ │ ├── .gitignore │ ├── .npmrc │ ├── .prettierignore │ ├── .prettierrc │ ├── .vscode/ │ │ └── extensions.json │ ├── README.md │ ├── eslint.config.js │ ├── flatpak/ │ │ ├── DevPod.metainfo.xml │ │ └── sh.loft.devpod.tmpl │ ├── index.html │ ├── package.json │ ├── src/ │ │ ├── App/ │ │ │ ├── App.tsx │ │ │ ├── Changelog.tsx │ │ │ ├── OSSApp.tsx │ │ │ ├── ProApp.tsx │ │ │ ├── constants.ts │ │ │ ├── index.ts │ │ │ ├── useAppReady.tsx │ │ │ ├── useChangelogModal.tsx │ │ │ └── usePreserveLocation.tsx │ │ ├── ProRoot.tsx │ │ ├── Theme/ │ │ │ ├── ThemeProvider.tsx │ │ │ ├── button.ts │ │ │ ├── card.ts │ │ │ ├── checkbox.ts │ │ │ ├── form.ts │ │ │ ├── index.ts │ │ │ ├── input.ts │ │ │ ├── menu.ts │ │ │ ├── modal.ts │ │ │ ├── popover.ts │ │ │ ├── radio.ts │ │ │ ├── select.ts │ │ │ ├── switch.ts │ │ │ ├── tabs.ts │ │ │ ├── tag.ts │ │ │ ├── text.ts │ │ │ ├── textarea.ts │ │ │ ├── theme.ts │ │ │ └── themeHooks.tsx │ │ ├── client/ │ │ │ ├── client.ts │ │ │ ├── command.ts │ │ │ ├── commandCache.ts │ │ │ ├── constants.ts │ │ │ ├── context/ │ │ │ │ ├── client.ts │ │ │ │ ├── contextCommands.ts │ │ │ │ └── index.ts │ │ │ ├── ides/ │ │ │ │ ├── client.ts │ │ │ │ ├── ideCommands.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── pro/ │ │ │ │ ├── client.ts │ │ │ │ ├── index.ts │ │ │ │ └── proCommands.ts │ │ │ ├── providers/ │ │ │ │ ├── client.ts │ │ │ │ ├── index.ts │ │ │ │ └── providerCommands.ts │ │ │ ├── tauriClient/ │ │ │ │ └── index.ts │ │ │ ├── types.ts │ │ │ └── workspaces/ │ │ │ ├── client.ts │ │ │ ├── index.ts │ │ │ └── workspaceCommands.ts │ │ ├── components/ │ │ │ ├── Animation/ │ │ │ │ ├── Ripple.tsx │ │ │ │ └── index.ts │ │ │ ├── AutoComplete/ │ │ │ │ ├── AutoComplete.tsx │ │ │ │ └── index.ts │ │ │ ├── BottomActionBar/ │ │ │ │ ├── BottomActionBar.tsx │ │ │ │ └── index.ts │ │ │ ├── CardHeader/ │ │ │ │ ├── WorkspaceCardHeader.tsx │ │ │ │ └── index.ts │ │ │ ├── DeleteWorkspacesModal/ │ │ │ │ ├── DeleteWorkspacesModal.tsx │ │ │ │ └── index.ts │ │ │ ├── Error/ │ │ │ │ ├── ErrorMessageBox.tsx │ │ │ │ └── index.ts │ │ │ ├── ExampleCard.tsx │ │ │ ├── Form/ │ │ │ │ ├── Form.tsx │ │ │ │ └── index.ts │ │ │ ├── IDEGroup/ │ │ │ │ ├── IDEGroup.tsx │ │ │ │ └── index.ts │ │ │ ├── IDEIcon/ │ │ │ │ ├── IDEIcon.tsx │ │ │ │ └── index.ts │ │ │ ├── Layout/ │ │ │ │ ├── NavigationViewLayout.tsx │ │ │ │ ├── Notifications.tsx │ │ │ │ ├── ProLayout.tsx │ │ │ │ ├── ProSwitcher.tsx │ │ │ │ ├── Sidebar.tsx │ │ │ │ ├── StatusBar.tsx │ │ │ │ ├── Toolbar.tsx │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── ListSelection/ │ │ │ │ ├── ListSelection.tsx │ │ │ │ └── index.ts │ │ │ ├── LoftOSSBadge.tsx │ │ │ ├── Section/ │ │ │ │ ├── CollapsibleSection.tsx │ │ │ │ └── index.ts │ │ │ ├── Steps/ │ │ │ │ ├── Steps.tsx │ │ │ │ └── index.ts │ │ │ ├── Tag/ │ │ │ │ ├── IconTag.tsx │ │ │ │ └── index.ts │ │ │ ├── Terminal/ │ │ │ │ ├── Terminal.tsx │ │ │ │ ├── TerminalSearchBar.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── useStreamingTerminal.tsx │ │ │ │ └── useTerminalSearch.tsx │ │ │ ├── Warning/ │ │ │ │ ├── WarningMessageBox.tsx │ │ │ │ └── index.ts │ │ │ ├── WorkspaceOwnerFilter/ │ │ │ │ ├── WorkspaceOwnerFilter.tsx │ │ │ │ └── index.ts │ │ │ ├── WorkspaceSorter/ │ │ │ │ ├── WorkspaceSorter.tsx │ │ │ │ └── index.ts │ │ │ ├── WorkspaceStatusFilter/ │ │ │ │ ├── WorkspaceStatusFilter.tsx │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ └── useInstallCLI.tsx │ │ ├── constants.ts │ │ ├── contexts/ │ │ │ ├── DevPodContext/ │ │ │ │ ├── DevPodProvider/ │ │ │ │ │ ├── DevPodContext.tsx │ │ │ │ │ ├── DevPodProvider.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Pro/ │ │ │ │ │ ├── ContextSwitcher.tsx │ │ │ │ │ ├── ProProvider.tsx │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useProContext.ts │ │ │ │ │ ├── useProHost.ts │ │ │ │ │ ├── useProjectClusters.tsx │ │ │ │ │ ├── useTemplates.ts │ │ │ │ │ └── workspaceInstance.ts │ │ │ │ ├── action/ │ │ │ │ │ ├── action.ts │ │ │ │ │ ├── actionHistory.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── useAction.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── helpers.ts │ │ │ │ ├── index.ts │ │ │ │ ├── proInstances/ │ │ │ │ │ ├── ProInstancesProvider.tsx │ │ │ │ │ ├── context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── useProInstanceManager.ts │ │ │ │ │ └── useProInstances.tsx │ │ │ │ ├── useProvider.ts │ │ │ │ ├── useProviderManager.ts │ │ │ │ ├── useProviders.ts │ │ │ │ ├── workspaceStore/ │ │ │ │ │ ├── WorkspaceStoreProvider.tsx │ │ │ │ │ ├── context.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── useWorkspaceStore.ts │ │ │ │ │ └── workspaceStore.ts │ │ │ │ └── workspaces/ │ │ │ │ ├── index.ts │ │ │ │ ├── useAllWorkspaceActions.ts │ │ │ │ ├── usePollWorkspaces.ts │ │ │ │ ├── useWorkspace.ts │ │ │ │ └── useWorkspaces.ts │ │ │ ├── SettingsContext/ │ │ │ │ ├── SettingsContext.tsx │ │ │ │ ├── index.ts │ │ │ │ └── useSettings.ts │ │ │ ├── ToolbarContext/ │ │ │ │ ├── ToolbarContext.tsx │ │ │ │ ├── index.ts │ │ │ │ └── useToolbar.ts │ │ │ └── index.ts │ │ ├── gen/ │ │ │ ├── Asset.ts │ │ │ ├── Author.ts │ │ │ ├── ColorMode.ts │ │ │ ├── DaemonState.ts │ │ │ ├── DaemonStatus.ts │ │ │ ├── Release.ts │ │ │ ├── Settings.ts │ │ │ ├── SidebarPosition.ts │ │ │ ├── Zoom.ts │ │ │ └── index.ts │ │ ├── icons/ │ │ │ ├── ArrowCycle.tsx │ │ │ ├── ArrowDown.tsx │ │ │ ├── ArrowLeft.tsx │ │ │ ├── ArrowPath.tsx │ │ │ ├── ArrowUp.tsx │ │ │ ├── Bell.tsx │ │ │ ├── BellDuotone.tsx │ │ │ ├── Briefcase.tsx │ │ │ ├── CPU.tsx │ │ │ ├── CheckCircle.tsx │ │ │ ├── CircleDuotone.tsx │ │ │ ├── CircleWithArrow.tsx │ │ │ ├── Clock.tsx │ │ │ ├── Close.tsx │ │ │ ├── Code.tsx │ │ │ ├── Cog.tsx │ │ │ ├── CogDuotone.tsx │ │ │ ├── CogOutlined.tsx │ │ │ ├── CommandLine.tsx │ │ │ ├── Connect.tsx │ │ │ ├── Dashboard.tsx │ │ │ ├── Database.tsx │ │ │ ├── DevPodProBadge.tsx │ │ │ ├── Devpod.tsx │ │ │ ├── DevpodWordmark.tsx │ │ │ ├── Ellipsis.tsx │ │ │ ├── ExclamationCircle.tsx │ │ │ ├── ExclamationTriangle.tsx │ │ │ ├── File.tsx │ │ │ ├── Folder.tsx │ │ │ ├── Form.tsx │ │ │ ├── Git.tsx │ │ │ ├── GitBranch.tsx │ │ │ ├── GitCommit.tsx │ │ │ ├── GitPR.tsx │ │ │ ├── GitSubPath.tsx │ │ │ ├── Globe.tsx │ │ │ ├── Gold.tsx │ │ │ ├── History.tsx │ │ │ ├── Image.tsx │ │ │ ├── Laptop.tsx │ │ │ ├── LockDuotone.tsx │ │ │ ├── Loft.tsx │ │ │ ├── LoftDevpodPro.tsx │ │ │ ├── MatchCase.tsx │ │ │ ├── Memory.tsx │ │ │ ├── NotFound.tsx │ │ │ ├── Parameters.tsx │ │ │ ├── Pause.tsx │ │ │ ├── Play.tsx │ │ │ ├── Plus.tsx │ │ │ ├── Preset.tsx │ │ │ ├── ProfileDuotone.tsx │ │ │ ├── ProviderPlaceholder.tsx │ │ │ ├── Search.tsx │ │ │ ├── Sleep.tsx │ │ │ ├── Stack3D.tsx │ │ │ ├── Status.tsx │ │ │ ├── Stop.tsx │ │ │ ├── Template.tsx │ │ │ ├── Trash.tsx │ │ │ ├── User.tsx │ │ │ ├── WholeWord.tsx │ │ │ ├── WorkspaceStatus.tsx │ │ │ ├── defaultProps.tsx │ │ │ └── index.ts │ │ ├── images/ │ │ │ └── index.ts │ │ ├── lib/ │ │ │ ├── debugSettings.ts │ │ │ ├── eventManager.ts │ │ │ ├── helpers.ts │ │ │ ├── index.ts │ │ │ ├── modals/ │ │ │ │ ├── index.ts │ │ │ │ ├── useDeleteWorkspaceModal.tsx │ │ │ │ ├── useLoginProModal.tsx │ │ │ │ ├── useRebuildWorkspaceModal.tsx │ │ │ │ ├── useResetWorkspaceModal.tsx │ │ │ │ └── useStopWorkspaceModal.tsx │ │ │ ├── platform.ts │ │ │ ├── pro/ │ │ │ │ ├── constants.ts │ │ │ │ ├── index.ts │ │ │ │ ├── name.ts │ │ │ │ ├── parameters.tsx │ │ │ │ ├── source.ts │ │ │ │ ├── time.ts │ │ │ │ └── useConnectionStatus.tsx │ │ │ ├── randomWords.ts │ │ │ ├── releases.ts │ │ │ ├── result.ts │ │ │ ├── store.ts │ │ │ ├── systemInfo.ts │ │ │ ├── types.ts │ │ │ ├── useDownloadLogs.ts │ │ │ ├── useFormErrors.ts │ │ │ ├── useHover.ts │ │ │ ├── useSelection.ts │ │ │ ├── useSortWorkspaces.tsx │ │ │ ├── useStoreTroubleshoot.ts │ │ │ ├── useUpdate.ts │ │ │ └── useVersion.ts │ │ ├── main.tsx │ │ ├── queryKeys.ts │ │ ├── routes.tsx │ │ ├── runner.ts │ │ ├── types.ts │ │ ├── useCommunityContributions.ts │ │ ├── useIDEs.ts │ │ ├── useWelcomeModal.tsx │ │ ├── views/ │ │ │ ├── Actions/ │ │ │ │ ├── Action.tsx │ │ │ │ ├── Actions.tsx │ │ │ │ ├── index.ts │ │ │ │ └── useActionTitle.tsx │ │ │ ├── Pro/ │ │ │ │ ├── BackToWorkspaces.tsx │ │ │ │ ├── CreateWorkspace/ │ │ │ │ │ ├── CreateWorkspace.tsx │ │ │ │ │ ├── CreateWorkspaceForm.tsx │ │ │ │ │ ├── DevContainerInput.tsx │ │ │ │ │ ├── IDEInput.tsx │ │ │ │ │ ├── InfrastructureTemplateInput.tsx │ │ │ │ │ ├── PresetInput.tsx │ │ │ │ │ ├── RunnerInput.tsx │ │ │ │ │ ├── SourceInput.tsx │ │ │ │ │ ├── UpdateWorkspace.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── Credentials/ │ │ │ │ │ ├── AddGitHTTPCredentials.tsx │ │ │ │ │ ├── AddGitSSHCredentials.tsx │ │ │ │ │ ├── Credentials.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── ListWorkspaces.tsx │ │ │ │ ├── ProInstance.tsx │ │ │ │ ├── Profile.tsx │ │ │ │ ├── SelectPreset/ │ │ │ │ │ ├── SelectPreset.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Settings.tsx │ │ │ │ ├── Workspace/ │ │ │ │ │ ├── Configuration.tsx │ │ │ │ │ ├── Logs.tsx │ │ │ │ │ ├── Tabs.tsx │ │ │ │ │ ├── Workspace.tsx │ │ │ │ │ ├── WorkspaceCardHeader.tsx │ │ │ │ │ ├── WorkspaceDetails.tsx │ │ │ │ │ ├── WorkspaceInstanceCard.tsx │ │ │ │ │ ├── WorkspaceStatus.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── status.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── useTemplate.ts │ │ │ │ ├── helpers.ts │ │ │ │ └── index.ts │ │ │ ├── Providers/ │ │ │ │ ├── AddProvider/ │ │ │ │ │ ├── ConfigureProviderOptionsForm.tsx │ │ │ │ │ ├── LoadingProviderIndicator.tsx │ │ │ │ │ ├── OptionFormField.tsx │ │ │ │ │ ├── SetupClonedProvider.tsx │ │ │ │ │ ├── SetupProviderSourceForm.tsx │ │ │ │ │ ├── SetupProviderSteps.tsx │ │ │ │ │ ├── helpers.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ ├── useAddProvider.ts │ │ │ │ │ ├── useProviderOptions.ts │ │ │ │ │ └── useSetupProvider.ts │ │ │ │ ├── ListProviders.tsx │ │ │ │ ├── Provider.tsx │ │ │ │ ├── ProviderCard.tsx │ │ │ │ ├── Providers.tsx │ │ │ │ ├── helpers.ts │ │ │ │ ├── index.ts │ │ │ │ ├── useDeleteProviderModal.tsx │ │ │ │ ├── useProviderTitle.tsx │ │ │ │ └── useSetupProviderModal.tsx │ │ │ ├── Settings/ │ │ │ │ ├── ClearableInput.tsx │ │ │ │ ├── Settings.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── useContextOptions.tsx │ │ │ │ ├── useIDESettings.tsx │ │ │ │ └── useSettingsOptions.tsx │ │ │ ├── Workspaces/ │ │ │ │ ├── CreateWorkspace/ │ │ │ │ │ ├── CreateWorkspace.tsx │ │ │ │ │ ├── ProviderOptionsPopover.tsx │ │ │ │ │ ├── WorkspaceSourceInput.tsx │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── useCreateWorkspaceForm.tsx │ │ │ │ ├── ListWorkspaces.tsx │ │ │ │ ├── WorkspaceCard.tsx │ │ │ │ ├── WorkspaceControls.tsx │ │ │ │ ├── WorkspaceStatusBadge.tsx │ │ │ │ ├── Workspaces.tsx │ │ │ │ ├── index.ts │ │ │ │ └── useWorkspaceTitle.tsx │ │ │ └── index.ts │ │ └── vite-env.d.ts │ ├── src-tauri/ │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ ├── Info.plist │ │ ├── bin/ │ │ │ └── .gitkeep │ │ ├── build.rs │ │ ├── capabilities/ │ │ │ ├── migrated.json │ │ │ └── updater.json │ │ ├── entitlements.plist │ │ ├── gen/ │ │ │ └── schemas/ │ │ │ ├── acl-manifests.json │ │ │ ├── capabilities.json │ │ │ ├── desktop-schema.json │ │ │ ├── linux-schema.json │ │ │ ├── macOS-schema.json │ │ │ └── windows-schema.json │ │ ├── icons/ │ │ │ └── icon.icns │ │ ├── sign.bat │ │ ├── src/ │ │ │ ├── action_logs.rs │ │ │ ├── commands/ │ │ │ │ ├── config.rs │ │ │ │ ├── constants.rs │ │ │ │ ├── delete_pro_instance.rs │ │ │ │ ├── delete_provider.rs │ │ │ │ ├── list_pro_instances.rs │ │ │ │ ├── list_workspaces.rs │ │ │ │ └── start_daemon.rs │ │ │ ├── commands.rs │ │ │ ├── community_contributions.rs │ │ │ ├── custom_protocol.rs │ │ │ ├── daemon/ │ │ │ │ └── client.rs │ │ │ ├── daemon.rs │ │ │ ├── file_exists.rs │ │ │ ├── fix_env.rs │ │ │ ├── get_env.rs │ │ │ ├── install_cli.rs │ │ │ ├── logging.rs │ │ │ ├── main.rs │ │ │ ├── providers.rs │ │ │ ├── resource_watcher.rs │ │ │ ├── server.rs │ │ │ ├── settings.rs │ │ │ ├── system_tray.rs │ │ │ ├── ui_messages.rs │ │ │ ├── ui_ready.rs │ │ │ ├── updates.rs │ │ │ ├── util.rs │ │ │ └── window.rs │ │ ├── tauri-dev-linux.conf.json │ │ ├── tauri-dev.conf.json │ │ ├── tauri-flatpak.conf.json │ │ └── tauri.conf.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── update-window/ │ │ ├── index.html │ │ └── src/ │ │ └── main.tsx │ └── vite.config.ts ├── docs/ │ ├── .gitignore │ ├── README.md │ ├── docusaurus.config.js │ ├── package.json │ ├── pages/ │ │ ├── developing-in-workspaces/ │ │ │ ├── connect-to-a-workspace.mdx │ │ │ ├── create-a-workspace.mdx │ │ │ ├── credentials.mdx │ │ │ ├── delete-a-workspace.mdx │ │ │ ├── devcontainer-json.mdx │ │ │ ├── dotfiles-in-a-workspace.mdx │ │ │ ├── environment-variables-in-devcontainer-json.mdx │ │ │ ├── inactivity-timeout.mdx │ │ │ ├── prebuild-a-workspace.mdx │ │ │ ├── stop-a-workspace.mdx │ │ │ └── what-are-workspaces.mdx │ │ ├── developing-providers/ │ │ │ ├── agent.mdx │ │ │ ├── binaries.mdx │ │ │ ├── driver.mdx │ │ │ ├── options.mdx │ │ │ └── quickstart.mdx │ │ ├── fragments/ │ │ │ ├── add-provider.mdx │ │ │ ├── setup-virtualbox.mdx │ │ │ └── virtualbox-ubuntu-22.04.mdx │ │ ├── getting-started/ │ │ │ ├── install.mdx │ │ │ ├── quickstart-devpod-cli.mdx │ │ │ ├── quickstart-jetbrains.mdx │ │ │ ├── quickstart-ssh.mdx │ │ │ ├── quickstart-vim.mdx │ │ │ ├── quickstart-vscode-browser.mdx │ │ │ ├── quickstart-vscode.mdx │ │ │ └── update.mdx │ │ ├── how-it-works/ │ │ │ ├── building-workspaces.mdx │ │ │ ├── deploy-k8s.mdx │ │ │ ├── deploy-machines.mdx │ │ │ ├── deploying-workspaces.mdx │ │ │ └── overview.mdx │ │ ├── licenses/ │ │ │ └── devpod.mdx │ │ ├── managing-machines/ │ │ │ ├── manage-machines.mdx │ │ │ └── what-are-machines.mdx │ │ ├── managing-providers/ │ │ │ ├── add-provider.mdx │ │ │ ├── delete-provider.mdx │ │ │ ├── update-provider.mdx │ │ │ └── what-are-providers.mdx │ │ ├── other-topics/ │ │ │ ├── advanced-guides/ │ │ │ │ └── minikube-vscode-browser.mdx │ │ │ ├── mobile-support.mdx │ │ │ └── telemetry.mdx │ │ ├── quickstart/ │ │ │ ├── browser.mdx │ │ │ ├── devpod-cli.mdx │ │ │ ├── jetbrains.mdx │ │ │ ├── ssh.mdx │ │ │ ├── vim.mdx │ │ │ └── vscode.mdx │ │ ├── troubleshooting/ │ │ │ ├── ide-troubleshooting.mdx │ │ │ ├── linux-troubleshooting.mdx │ │ │ ├── troubleshooting.mdx │ │ │ └── windows-troubleshooting.mdx │ │ ├── tutorials/ │ │ │ ├── docker-provider-via-wsl.mdx │ │ │ ├── minikube-vscode-browser.mdx │ │ │ └── reduce-build-times-with-cache.mdx │ │ └── what-is-devpod.mdx │ ├── public/ │ │ └── _redirects │ ├── sidebars.js │ ├── src/ │ │ ├── components/ │ │ │ ├── Highlight/ │ │ │ │ ├── Highlight.js │ │ │ │ └── styles.module.css │ │ │ └── Step/ │ │ │ ├── Step.js │ │ │ └── styles.module.css │ │ ├── css/ │ │ │ └── custom.css │ │ └── pages/ │ │ ├── index.js │ │ └── styles.module.css │ ├── static/ │ │ └── js/ │ │ └── custom.js │ └── uml/ │ ├── c4_build.puml │ ├── c4_build_k8s.puml │ ├── c4_k8s.puml │ ├── c4_machines.puml │ ├── c4_workspaces.puml │ └── up_sequence.puml ├── e2e/ │ ├── README.md │ ├── devcontainer-feature.json │ ├── e2e_suite_test.go │ ├── framework/ │ │ ├── command.go │ │ ├── exec.go │ │ ├── framework.go │ │ ├── helper.go │ │ ├── server_utils.go │ │ ├── types.go │ │ └── util.go │ └── tests/ │ ├── build/ │ │ ├── build.go │ │ ├── framework.go │ │ └── testdata/ │ │ ├── docker/ │ │ │ └── .devcontainer/ │ │ │ ├── Dockerfile │ │ │ └── devcontainer.json │ │ ├── docker-compose/ │ │ │ └── .devcontainer/ │ │ │ ├── Dockerfile │ │ │ ├── devcontainer.json │ │ │ └── docker-compose.yaml │ │ └── kubernetes/ │ │ └── .devcontainer/ │ │ ├── Dockerfile │ │ └── devcontainer.json │ ├── context/ │ │ ├── context.go │ │ └── framework.go │ ├── ide/ │ │ ├── framework.go │ │ ├── ide.go │ │ └── testdata/ │ │ └── .devcontainer.json │ ├── integration/ │ │ ├── framework.go │ │ ├── integration.go │ │ └── testdata/ │ │ └── .devcontainer.json │ ├── machine/ │ │ ├── create.go │ │ ├── delete.go │ │ ├── framework.go │ │ └── testdata/ │ │ └── mock-provider.yaml │ ├── machineprovider/ │ │ ├── framework.go │ │ ├── machineprovider.go │ │ └── testdata/ │ │ ├── machineprovider/ │ │ │ ├── .devcontainer.json │ │ │ ├── provider.yaml │ │ │ └── test.txt │ │ └── machineprovider2/ │ │ ├── .devcontainer.json │ │ ├── provider.yaml │ │ └── test.txt │ ├── provider/ │ │ ├── framework.go │ │ ├── provider.go │ │ └── testdata/ │ │ └── simple-k8s-provider/ │ │ ├── provider1.yaml │ │ ├── provider2-update.yaml │ │ ├── provider2.yaml │ │ └── provider3.yaml │ ├── ssh/ │ │ ├── framework.go │ │ ├── ssh.go │ │ └── testdata/ │ │ ├── forward-test/ │ │ │ └── .devcontainer.json │ │ ├── gpg-forwarding/ │ │ │ ├── .devcontainer.json │ │ │ ├── devcontainer.json │ │ │ ├── gpg-private.key │ │ │ └── gpg-public.key │ │ └── local-test/ │ │ └── devcontainer.json │ └── up/ │ ├── docker-wsl.go │ ├── docker.go │ ├── docker_build.go │ ├── docker_compose.go │ ├── docker_compose_build.go │ ├── framework.go │ ├── helper.go │ ├── podman.go │ ├── testdata/ │ │ ├── docker/ │ │ │ └── .devcontainer.json │ │ ├── docker-compose/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-capadd/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-container-env/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-container-user/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-env-file/ │ │ │ ├── .devcontainer/ │ │ │ │ └── devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-extensions/ │ │ │ ├── .devcontainer/ │ │ │ │ └── devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-features/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-forward-ports/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-lifecycle-array/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-lifecycle-object/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-mounts/ │ │ │ ├── .devcontainer.json │ │ │ ├── docker-compose.yaml │ │ │ └── mount2/ │ │ │ └── bar.txt │ │ ├── docker-compose-multiple-services/ │ │ │ ├── .devcontainer/ │ │ │ │ └── devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-override-command/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-overrides/ │ │ │ ├── .devcontainer/ │ │ │ │ ├── devcontainer.json │ │ │ │ └── docker-compose.devcontainer.yaml │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-privileged/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-rebuild-fail/ │ │ │ ├── .devcontainer.json │ │ │ ├── docker-compose.yaml │ │ │ └── fail.devcontainer.json │ │ ├── docker-compose-rebuild-success/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-remote-env/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-remote-user/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-run-services/ │ │ │ ├── .devcontainer/ │ │ │ │ └── devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-securityOpt/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-subfolder/ │ │ │ ├── .devcontainer/ │ │ │ │ └── devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-variables/ │ │ │ ├── .devcontainer.json │ │ │ └── docker-compose.yaml │ │ ├── docker-compose-with-multi-stage-build/ │ │ │ ├── .devcontainer.json │ │ │ ├── Dockerfile │ │ │ └── docker-compose.yaml │ │ ├── docker-dockerfile-buildcontext/ │ │ │ ├── .devcontainer/ │ │ │ │ └── devcontainer.json │ │ │ ├── .dockerignore │ │ │ ├── Dockerfile │ │ │ └── scripts/ │ │ │ ├── alias.sh │ │ │ └── install.sh │ │ ├── docker-features-http-headers/ │ │ │ ├── .devcontainer.json │ │ │ ├── devcontainer-feature.json │ │ │ └── install.sh │ │ ├── docker-features-lifecycle-hooks/ │ │ │ └── .devcontainer.json │ │ ├── docker-mounts/ │ │ │ ├── .devcontainer.json │ │ │ ├── mount1/ │ │ │ │ └── foo.txt │ │ │ └── mount2/ │ │ │ └── bar.txt │ │ ├── docker-variables/ │ │ │ └── .devcontainer.json │ │ ├── docker-with-multi-stage-build/ │ │ │ ├── .devcontainer.json │ │ │ └── Dockerfile │ │ ├── kubernetes/ │ │ │ ├── .devcontainer.json │ │ │ └── test_file.txt │ │ └── no-devcontainer/ │ │ └── empty.go │ └── up.go ├── examples/ │ ├── build/ │ │ ├── .devcontainer.json │ │ ├── Dockerfile │ │ ├── README.md │ │ └── app/ │ │ └── code │ ├── build-multi-stage/ │ │ ├── .devcontainer.json │ │ ├── Dockerfile │ │ └── README.md │ ├── compose/ │ │ ├── .devcontainer.json │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── devcontainer.Dockerfile │ │ ├── devcontainer.Dockerfile2 │ │ └── docker-compose.devcontainer.yml │ ├── feature/ │ │ ├── .devcontainer.json │ │ └── afeature/ │ │ ├── devcontainer-feature.json │ │ └── install.sh │ ├── multi-devcontainer/ │ │ ├── .devcontainer.json │ │ ├── proj1/ │ │ │ └── .devcontainer.json │ │ └── proj2/ │ │ └── .devcontainer.json │ ├── object-lifecycle-hooks/ │ │ └── .devcontainer.json │ ├── simple/ │ │ ├── .devcontainer.json │ │ └── README.md │ ├── simple-k8s-provider/ │ │ ├── README.md │ │ └── provider.yaml │ └── ztunnel/ │ ├── README.md │ ├── devcontainer.json │ ├── helloworld.yaml │ ├── launch.json │ ├── pod_manifest.yaml │ └── sleep.yaml ├── go.mod ├── go.sum ├── hack/ │ ├── build-e2e.sh │ ├── build-grpc.sh │ ├── dev_devpod_pro.sh │ ├── gen-desktop.sh │ ├── pro/ │ │ ├── main.go │ │ └── provider.yaml │ ├── rebuild.sh │ └── unit-tests.sh ├── loadtest/ │ ├── README.md │ ├── deleteWorkspaces.sh │ ├── emulateTraffic.sh │ ├── generateLoad.sh │ ├── init_monitor.sh │ ├── monitor.sh │ ├── run.sh │ └── startWorkspaces.sh ├── main.go ├── netlify.toml ├── pkg/ │ ├── agent/ │ │ ├── agent.go │ │ ├── inject.go │ │ ├── tunnel/ │ │ │ ├── tunnel.pb.go │ │ │ ├── tunnel.proto │ │ │ └── tunnel_grpc.pb.go │ │ ├── tunnelserver/ │ │ │ ├── client.go │ │ │ ├── logger.go │ │ │ ├── options.go │ │ │ ├── stream.go │ │ │ └── tunnelserver.go │ │ └── workspace.go │ ├── binaries/ │ │ └── download.go │ ├── client/ │ │ ├── client.go │ │ └── clientimplementation/ │ │ ├── daemonclient/ │ │ │ ├── client.go │ │ │ ├── create.go │ │ │ ├── delete.go │ │ │ ├── form.go │ │ │ ├── status.go │ │ │ ├── stop.go │ │ │ ├── up.go │ │ │ └── update.go │ │ ├── machine_client.go │ │ ├── proxy_client.go │ │ └── workspace_client.go │ ├── command/ │ │ ├── command.go │ │ ├── escape.go │ │ ├── process.go │ │ ├── process_supported.go │ │ ├── process_unsupported.go │ │ └── user.go │ ├── compose/ │ │ └── helper.go │ ├── compress/ │ │ └── compress.go │ ├── config/ │ │ ├── config.go │ │ ├── context.go │ │ ├── context_test.go │ │ ├── dir.go │ │ └── ide.go │ ├── copy/ │ │ ├── copy.go │ │ ├── copy_supported.go │ │ └── copy_unsupported.go │ ├── credentials/ │ │ ├── request.go │ │ ├── server.go │ │ └── start.go │ ├── daemon/ │ │ ├── agent/ │ │ │ ├── daemon.go │ │ │ ├── reaper_linux.go │ │ │ └── reaper_stub.go │ │ └── platform/ │ │ ├── client.go │ │ ├── daemon.go │ │ ├── error.go │ │ ├── error_unix.go │ │ ├── error_windows.go │ │ ├── local_server.go │ │ ├── socket.go │ │ ├── socket_windows.go │ │ ├── ts_server.go │ │ └── workspace_watcher.go │ ├── devcontainer/ │ │ ├── build/ │ │ │ └── options.go │ │ ├── build.go │ │ ├── build_test.go │ │ ├── buildkit/ │ │ │ ├── buildkit.go │ │ │ ├── cache.go │ │ │ ├── conn.go │ │ │ ├── printer.go │ │ │ └── remote.go │ │ ├── compose.go │ │ ├── config/ │ │ │ ├── build.go │ │ │ ├── config.go │ │ │ ├── container_details.go │ │ │ ├── feature.go │ │ │ ├── merge.go │ │ │ ├── metadata.go │ │ │ ├── parse.go │ │ │ ├── parse_test.go │ │ │ ├── prebuild.go │ │ │ ├── prepareprobe.go │ │ │ ├── prepareprobe_windows.go │ │ │ ├── result.go │ │ │ ├── substitute.go │ │ │ └── userenvprobe.go │ │ ├── config.go │ │ ├── crane/ │ │ │ └── run.go │ │ ├── delete.go │ │ ├── feature/ │ │ │ ├── extend.go │ │ │ ├── features.go │ │ │ └── options.go │ │ ├── graph/ │ │ │ ├── graph.go │ │ │ └── graph_test.go │ │ ├── helpers.go │ │ ├── inspect.go │ │ ├── metadata/ │ │ │ └── metadata.go │ │ ├── prebuild.go │ │ ├── run.go │ │ ├── setup/ │ │ │ ├── lifecyclehooks.go │ │ │ └── setup.go │ │ ├── setup.go │ │ ├── single.go │ │ └── sshtunnel/ │ │ └── sshtunnel.go │ ├── docker/ │ │ ├── client.go │ │ ├── config.go │ │ └── helper.go │ ├── dockercredentials/ │ │ └── dockercredentials.go │ ├── dockerfile/ │ │ ├── parse.go │ │ ├── parse_test.go │ │ └── test_Dockerfile │ ├── download/ │ │ └── download.go │ ├── driver/ │ │ ├── custom/ │ │ │ └── custom.go │ │ ├── docker/ │ │ │ ├── build.go │ │ │ └── docker.go │ │ ├── docker.go │ │ ├── drivercreate/ │ │ │ └── create.go │ │ ├── kubernetes/ │ │ │ ├── client.go │ │ │ ├── container_status.go │ │ │ ├── daemonsecret.go │ │ │ ├── dockersecrets.go │ │ │ ├── driver.go │ │ │ ├── find.go │ │ │ ├── helper.go │ │ │ ├── init_container.go │ │ │ ├── pullsecrets.go │ │ │ ├── pvc.go │ │ │ ├── registry.go │ │ │ ├── registry_test.go │ │ │ ├── run.go │ │ │ ├── serviceaccount.go │ │ │ ├── target_architecture.go │ │ │ ├── throttledlogger/ │ │ │ │ ├── throttled_logger.go │ │ │ │ ├── timer.go │ │ │ │ └── timer_test.go │ │ │ └── wait.go │ │ └── types.go │ ├── encoding/ │ │ ├── encoding.go │ │ └── encoding_test.go │ ├── envfile/ │ │ └── envfile.go │ ├── extract/ │ │ ├── compress.go │ │ ├── extract.go │ │ └── zip.go │ ├── file/ │ │ ├── file.go │ │ ├── file_supported.go │ │ └── file_unsupported.go │ ├── git/ │ │ ├── clone.go │ │ ├── git.go │ │ ├── git_test.go │ │ └── install.go │ ├── gitcredentials/ │ │ └── gitcredentials.go │ ├── gitsshsigning/ │ │ ├── client.go │ │ ├── helper.go │ │ ├── server.go │ │ └── utils.go │ ├── gpg/ │ │ └── gpg_forwarding.go │ ├── http/ │ │ └── http.go │ ├── id/ │ │ └── id.go │ ├── ide/ │ │ ├── fleet/ │ │ │ └── fleet.go │ │ ├── ideparse/ │ │ │ └── parse.go │ │ ├── jetbrains/ │ │ │ ├── clion.go │ │ │ ├── dataspell.go │ │ │ ├── generic.go │ │ │ ├── goland.go │ │ │ ├── intellij.go │ │ │ ├── phpstorm.go │ │ │ ├── pycharm.go │ │ │ ├── rider.go │ │ │ ├── rubymine.go │ │ │ ├── rustrover.go │ │ │ └── webstorm.go │ │ ├── jupyter/ │ │ │ └── jupyter.go │ │ ├── openvscode/ │ │ │ └── openvscode.go │ │ ├── rstudio/ │ │ │ └── rstudio.go │ │ ├── types.go │ │ ├── vscode/ │ │ │ ├── apk.go │ │ │ ├── open.go │ │ │ └── vscode.go │ │ └── zed/ │ │ ├── zed.go │ │ └── zed_linux.go │ ├── image/ │ │ ├── auth.go │ │ └── image.go │ ├── inject/ │ │ ├── delayed_writer.go │ │ ├── download_urls.go │ │ ├── inject.go │ │ ├── inject.sh │ │ └── script.go │ ├── language/ │ │ └── language.go │ ├── loftconfig/ │ │ ├── client.go │ │ ├── config.go │ │ └── server.go │ ├── log/ │ │ ├── jsonstream.go │ │ └── log.go │ ├── netstat/ │ │ ├── netstat.go │ │ ├── netstat_util.go │ │ └── watcher.go │ ├── open/ │ │ └── open.go │ ├── options/ │ │ ├── resolve.go │ │ ├── resolve_test.go │ │ └── resolver/ │ │ ├── parse.go │ │ ├── resolve.go │ │ ├── resolver.go │ │ ├── sub_options.go │ │ └── util.go │ ├── platform/ │ │ ├── annotations/ │ │ │ └── annotations.go │ │ ├── client/ │ │ │ ├── client.go │ │ │ └── config.go │ │ ├── client.go │ │ ├── config.go │ │ ├── deploy.go │ │ ├── env.go │ │ ├── form/ │ │ │ └── form.go │ │ ├── instance.go │ │ ├── kube/ │ │ │ └── kube.go │ │ ├── kubeconfig.go │ │ ├── labels/ │ │ │ └── labels.go │ │ ├── option.go │ │ ├── owner.go │ │ ├── parameters/ │ │ │ └── parameters.go │ │ ├── platform.go │ │ ├── project/ │ │ │ └── project.go │ │ ├── remotecommand/ │ │ │ ├── client.go │ │ │ ├── protocol.go │ │ │ ├── remotecommand.go │ │ │ ├── stream.go │ │ │ └── websocket.go │ │ ├── user.go │ │ └── version.go │ ├── port/ │ │ ├── parse.go │ │ └── port.go │ ├── provider/ │ │ ├── dir.go │ │ ├── env.go │ │ ├── export.go │ │ ├── machine.go │ │ ├── parse.go │ │ ├── pro.go │ │ ├── provider.go │ │ └── workspace.go │ ├── random/ │ │ └── random.go │ ├── shell/ │ │ └── shell.go │ ├── single/ │ │ └── single.go │ ├── ssh/ │ │ ├── agent/ │ │ │ ├── agent_unix.go │ │ │ └── agent_windows.go │ │ ├── config.go │ │ ├── config_test.go │ │ ├── connection_counter.go │ │ ├── forward.go │ │ ├── helper.go │ │ ├── keys.go │ │ ├── server/ │ │ │ ├── agent.go │ │ │ ├── exec.go │ │ │ ├── exit.go │ │ │ ├── port/ │ │ │ │ └── port.go │ │ │ ├── pty_supported.go │ │ │ ├── pty_unsupported.go │ │ │ ├── sftp_handler.go │ │ │ ├── ssh.go │ │ │ ├── ssh_container.go │ │ │ └── workdir.go │ │ ├── ssh_add.go │ │ ├── ssh_supported.go │ │ └── ssh_unsupported.go │ ├── stdio/ │ │ ├── conn.go │ │ └── listener.go │ ├── telemetry/ │ │ ├── collect.go │ │ ├── helpers.go │ │ └── noop.go │ ├── template/ │ │ └── fill.go │ ├── token/ │ │ └── token.go │ ├── ts/ │ │ ├── addr.go │ │ ├── derp.go │ │ ├── ssh.go │ │ ├── util.go │ │ └── workspace_server.go │ ├── tunnel/ │ │ ├── container.go │ │ ├── direct.go │ │ ├── forwarder.go │ │ └── services.go │ ├── types/ │ │ ├── option.go │ │ ├── streams.go │ │ ├── time.go │ │ ├── types.go │ │ └── types_test.go │ ├── upgrade/ │ │ └── upgrade.go │ ├── util/ │ │ ├── hash/ │ │ │ └── hash.go │ │ ├── homedir.go │ │ ├── homedir_test.go │ │ └── rand.go │ ├── version/ │ │ └── version.go │ └── workspace/ │ ├── delete.go │ ├── id.go │ ├── id_test.go │ ├── image.go │ ├── list.go │ ├── machine.go │ ├── pro.go │ ├── provider.go │ └── workspace.go ├── providers/ │ ├── docker/ │ │ └── provider.yaml │ ├── kubernetes/ │ │ └── provider.yaml │ ├── pro/ │ │ └── provider.yaml │ └── providers.go ├── scripts/ │ ├── install_docker.sh │ └── scripts.go └── vendor/ ├── cel.dev/ │ └── expr/ │ ├── .bazelversion │ ├── .gitattributes │ ├── .gitignore │ ├── BUILD.bazel │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── GOVERNANCE.md │ ├── LICENSE │ ├── MAINTAINERS.md │ ├── MODULE.bazel │ ├── README.md │ ├── WORKSPACE │ ├── WORKSPACE.bzlmod │ ├── checked.pb.go │ ├── cloudbuild.yaml │ ├── eval.pb.go │ ├── explain.pb.go │ ├── regen_go_proto.sh │ ├── regen_go_proto_canonical_protos.sh │ ├── syntax.pb.go │ └── value.pb.go ├── cloud.google.com/ │ └── go/ │ └── compute/ │ └── metadata/ │ ├── CHANGES.md │ ├── LICENSE │ ├── README.md │ ├── metadata.go │ ├── retry.go │ ├── retry_linux.go │ ├── syscheck.go │ ├── syscheck_linux.go │ └── syscheck_windows.go ├── filippo.io/ │ └── edwards25519/ │ ├── LICENSE │ ├── README.md │ ├── doc.go │ ├── edwards25519.go │ ├── extra.go │ ├── field/ │ │ ├── fe.go │ │ ├── fe_amd64.go │ │ ├── fe_amd64.s │ │ ├── fe_amd64_noasm.go │ │ ├── fe_arm64.go │ │ ├── fe_arm64.s │ │ ├── fe_arm64_noasm.go │ │ ├── fe_extra.go │ │ └── fe_generic.go │ ├── scalar.go │ ├── scalar_fiat.go │ ├── scalarmult.go │ └── tables.go ├── github.com/ │ ├── AdaLogics/ │ │ └── go-fuzz-headers/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── consumer.go │ │ ├── funcs.go │ │ └── sql.go │ ├── AlecAivazis/ │ │ └── survey/ │ │ └── v2/ │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── confirm.go │ │ ├── core/ │ │ │ ├── template.go │ │ │ └── write.go │ │ ├── editor.go │ │ ├── filter.go │ │ ├── input.go │ │ ├── multiline.go │ │ ├── multiselect.go │ │ ├── password.go │ │ ├── renderer.go │ │ ├── select.go │ │ ├── survey.go │ │ ├── terminal/ │ │ │ ├── LICENSE.txt │ │ │ ├── README.md │ │ │ ├── buffered_reader.go │ │ │ ├── cursor.go │ │ │ ├── cursor_windows.go │ │ │ ├── display.go │ │ │ ├── display_posix.go │ │ │ ├── display_windows.go │ │ │ ├── error.go │ │ │ ├── output.go │ │ │ ├── output_windows.go │ │ │ ├── runereader.go │ │ │ ├── runereader_bsd.go │ │ │ ├── runereader_linux.go │ │ │ ├── runereader_posix.go │ │ │ ├── runereader_ppc64le.go │ │ │ ├── runereader_windows.go │ │ │ ├── sequences.go │ │ │ ├── stdio.go │ │ │ ├── syscall_windows.go │ │ │ └── terminal.go │ │ ├── transform.go │ │ └── validate.go │ ├── Azure/ │ │ ├── azure-sdk-for-go/ │ │ │ ├── LICENSE.txt │ │ │ ├── NOTICE.txt │ │ │ ├── services/ │ │ │ │ └── preview/ │ │ │ │ └── containerregistry/ │ │ │ │ └── runtime/ │ │ │ │ └── 2019-08-15-preview/ │ │ │ │ └── containerregistry/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── accesstokens.go │ │ │ │ ├── blob.go │ │ │ │ ├── client.go │ │ │ │ ├── dataplane_meta.json │ │ │ │ ├── manifests.go │ │ │ │ ├── models.go │ │ │ │ ├── refreshtokens.go │ │ │ │ ├── repository.go │ │ │ │ ├── tag.go │ │ │ │ ├── v2support.go │ │ │ │ └── version.go │ │ │ └── version/ │ │ │ └── version.go │ │ └── go-ansiterm/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── constants.go │ │ ├── context.go │ │ ├── csi_entry_state.go │ │ ├── csi_param_state.go │ │ ├── escape_intermediate_state.go │ │ ├── escape_state.go │ │ ├── event_handler.go │ │ ├── ground_state.go │ │ ├── osc_string_state.go │ │ ├── parser.go │ │ ├── parser_action_helpers.go │ │ ├── parser_actions.go │ │ ├── states.go │ │ ├── utilities.go │ │ └── winterm/ │ │ ├── ansi.go │ │ ├── api.go │ │ ├── attr_translation.go │ │ ├── cursor_helpers.go │ │ ├── erase_helpers.go │ │ ├── scroll_helper.go │ │ ├── utilities.go │ │ └── win_event_handler.go │ ├── BurntSushi/ │ │ └── toml/ │ │ ├── .gitignore │ │ ├── COPYING │ │ ├── README.md │ │ ├── decode.go │ │ ├── deprecated.go │ │ ├── doc.go │ │ ├── encode.go │ │ ├── error.go │ │ ├── internal/ │ │ │ └── tz.go │ │ ├── lex.go │ │ ├── meta.go │ │ ├── parse.go │ │ ├── type_fields.go │ │ └── type_toml.go │ ├── Microsoft/ │ │ └── go-winio/ │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── CODEOWNERS │ │ ├── LICENSE │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── backup.go │ │ ├── doc.go │ │ ├── ea.go │ │ ├── file.go │ │ ├── fileinfo.go │ │ ├── hvsock.go │ │ ├── internal/ │ │ │ ├── fs/ │ │ │ │ ├── doc.go │ │ │ │ ├── fs.go │ │ │ │ ├── security.go │ │ │ │ └── zsyscall_windows.go │ │ │ ├── socket/ │ │ │ │ ├── rawaddr.go │ │ │ │ ├── socket.go │ │ │ │ └── zsyscall_windows.go │ │ │ └── stringbuffer/ │ │ │ └── wstring.go │ │ ├── pipe.go │ │ ├── pkg/ │ │ │ └── guid/ │ │ │ ├── guid.go │ │ │ ├── guid_nonwindows.go │ │ │ ├── guid_windows.go │ │ │ └── variant_string.go │ │ ├── privilege.go │ │ ├── reparse.go │ │ ├── sd.go │ │ ├── syscall.go │ │ └── zsyscall_windows.go │ ├── NYTimes/ │ │ └── gziphandler/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── CODE_OF_CONDUCT.md │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── gzip.go │ │ └── gzip_go18.go │ ├── PaesslerAG/ │ │ ├── gval/ │ │ │ ├── .gitignore │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── evaluable.go │ │ │ ├── functions.go │ │ │ ├── gval.go │ │ │ ├── language.go │ │ │ ├── operator.go │ │ │ ├── parse.go │ │ │ └── parser.go │ │ └── jsonpath/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── jsonpath.go │ │ ├── parse.go │ │ ├── path.go │ │ ├── placeholder.go │ │ ├── selector.go │ │ └── test.sh │ ├── acarl005/ │ │ └── stripansi/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── stripansi.go │ ├── akutz/ │ │ └── memconn/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── VERSION │ │ ├── memconn.go │ │ ├── memconn_addr.go │ │ ├── memconn_conn.go │ │ ├── memconn_listener.go │ │ ├── memconn_pipe.go │ │ └── memconn_provider.go │ ├── alessio/ │ │ └── shellescape/ │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── .goreleaser.yml │ │ ├── AUTHORS │ │ ├── CODE_OF_CONDUCT.md │ │ ├── LICENSE │ │ ├── README.md │ │ └── shellescape.go │ ├── alexbrainman/ │ │ └── sspi/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── buffer.go │ │ ├── internal/ │ │ │ └── common/ │ │ │ └── common.go │ │ ├── mksyscall.go │ │ ├── negotiate/ │ │ │ └── negotiate.go │ │ ├── sspi.go │ │ ├── syscall.go │ │ └── zsyscall_windows.go │ ├── anmitsu/ │ │ └── go-shlex/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ └── shlex.go │ ├── antlr4-go/ │ │ └── antlr/ │ │ └── v4/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── antlrdoc.go │ │ ├── atn.go │ │ ├── atn_config.go │ │ ├── atn_config_set.go │ │ ├── atn_deserialization_options.go │ │ ├── atn_deserializer.go │ │ ├── atn_simulator.go │ │ ├── atn_state.go │ │ ├── atn_type.go │ │ ├── char_stream.go │ │ ├── common_token_factory.go │ │ ├── common_token_stream.go │ │ ├── comparators.go │ │ ├── configuration.go │ │ ├── dfa.go │ │ ├── dfa_serializer.go │ │ ├── dfa_state.go │ │ ├── diagnostic_error_listener.go │ │ ├── error_listener.go │ │ ├── error_strategy.go │ │ ├── errors.go │ │ ├── file_stream.go │ │ ├── input_stream.go │ │ ├── int_stream.go │ │ ├── interval_set.go │ │ ├── jcollect.go │ │ ├── lexer.go │ │ ├── lexer_action.go │ │ ├── lexer_action_executor.go │ │ ├── lexer_atn_simulator.go │ │ ├── ll1_analyzer.go │ │ ├── mutex.go │ │ ├── mutex_nomutex.go │ │ ├── nostatistics.go │ │ ├── parser.go │ │ ├── parser_atn_simulator.go │ │ ├── parser_rule_context.go │ │ ├── prediction_context.go │ │ ├── prediction_context_cache.go │ │ ├── prediction_mode.go │ │ ├── recognizer.go │ │ ├── rule_context.go │ │ ├── semantic_context.go │ │ ├── statistics.go │ │ ├── stats_data.go │ │ ├── token.go │ │ ├── token_source.go │ │ ├── token_stream.go │ │ ├── tokenstream_rewriter.go │ │ ├── trace_listener.go │ │ ├── transition.go │ │ ├── tree.go │ │ ├── trees.go │ │ └── utils.go │ ├── asaskevich/ │ │ └── govalidator/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── CODE_OF_CONDUCT.md │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── arrays.go │ │ ├── converter.go │ │ ├── doc.go │ │ ├── error.go │ │ ├── numerics.go │ │ ├── patterns.go │ │ ├── types.go │ │ ├── utils.go │ │ ├── validator.go │ │ └── wercker.yml │ ├── atotto/ │ │ └── clipboard/ │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── clipboard.go │ │ ├── clipboard_darwin.go │ │ ├── clipboard_plan9.go │ │ ├── clipboard_unix.go │ │ └── clipboard_windows.go │ ├── aws/ │ │ ├── aws-sdk-go-v2/ │ │ │ ├── LICENSE.txt │ │ │ ├── NOTICE.txt │ │ │ ├── aws/ │ │ │ │ ├── accountid_endpoint_mode.go │ │ │ │ ├── arn/ │ │ │ │ │ └── arn.go │ │ │ │ ├── config.go │ │ │ │ ├── context.go │ │ │ │ ├── credential_cache.go │ │ │ │ ├── credentials.go │ │ │ │ ├── defaults/ │ │ │ │ │ ├── auto.go │ │ │ │ │ ├── configuration.go │ │ │ │ │ ├── defaults.go │ │ │ │ │ └── doc.go │ │ │ │ ├── defaultsmode.go │ │ │ │ ├── doc.go │ │ │ │ ├── endpoints.go │ │ │ │ ├── errors.go │ │ │ │ ├── from_ptr.go │ │ │ │ ├── go_module_metadata.go │ │ │ │ ├── logging.go │ │ │ │ ├── logging_generate.go │ │ │ │ ├── middleware/ │ │ │ │ │ ├── metadata.go │ │ │ │ │ ├── middleware.go │ │ │ │ │ ├── osname.go │ │ │ │ │ ├── osname_go115.go │ │ │ │ │ ├── private/ │ │ │ │ │ │ └── metrics/ │ │ │ │ │ │ └── metrics.go │ │ │ │ │ ├── recursion_detection.go │ │ │ │ │ ├── request_id.go │ │ │ │ │ ├── request_id_retriever.go │ │ │ │ │ └── user_agent.go │ │ │ │ ├── protocol/ │ │ │ │ │ ├── query/ │ │ │ │ │ │ ├── array.go │ │ │ │ │ │ ├── encoder.go │ │ │ │ │ │ ├── map.go │ │ │ │ │ │ ├── middleware.go │ │ │ │ │ │ ├── object.go │ │ │ │ │ │ └── value.go │ │ │ │ │ ├── restjson/ │ │ │ │ │ │ └── decoder_util.go │ │ │ │ │ └── xml/ │ │ │ │ │ └── error_utils.go │ │ │ │ ├── ratelimit/ │ │ │ │ │ ├── none.go │ │ │ │ │ ├── token_bucket.go │ │ │ │ │ └── token_rate_limit.go │ │ │ │ ├── request.go │ │ │ │ ├── retry/ │ │ │ │ │ ├── adaptive.go │ │ │ │ │ ├── adaptive_ratelimit.go │ │ │ │ │ ├── adaptive_token_bucket.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── errors.go │ │ │ │ │ ├── jitter_backoff.go │ │ │ │ │ ├── metadata.go │ │ │ │ │ ├── middleware.go │ │ │ │ │ ├── retry.go │ │ │ │ │ ├── retryable_error.go │ │ │ │ │ ├── standard.go │ │ │ │ │ ├── throttle_error.go │ │ │ │ │ └── timeout_error.go │ │ │ │ ├── retryer.go │ │ │ │ ├── runtime.go │ │ │ │ ├── signer/ │ │ │ │ │ ├── internal/ │ │ │ │ │ │ └── v4/ │ │ │ │ │ │ ├── cache.go │ │ │ │ │ │ ├── const.go │ │ │ │ │ │ ├── header_rules.go │ │ │ │ │ │ ├── headers.go │ │ │ │ │ │ ├── hmac.go │ │ │ │ │ │ ├── host.go │ │ │ │ │ │ ├── scope.go │ │ │ │ │ │ ├── time.go │ │ │ │ │ │ └── util.go │ │ │ │ │ └── v4/ │ │ │ │ │ ├── middleware.go │ │ │ │ │ ├── presign_middleware.go │ │ │ │ │ ├── stream.go │ │ │ │ │ └── v4.go │ │ │ │ ├── to_ptr.go │ │ │ │ ├── transport/ │ │ │ │ │ └── http/ │ │ │ │ │ ├── client.go │ │ │ │ │ ├── content_type.go │ │ │ │ │ ├── response_error.go │ │ │ │ │ ├── response_error_middleware.go │ │ │ │ │ └── timeout_read_closer.go │ │ │ │ ├── types.go │ │ │ │ └── version.go │ │ │ ├── config/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── config.go │ │ │ │ ├── defaultsmode.go │ │ │ │ ├── doc.go │ │ │ │ ├── env_config.go │ │ │ │ ├── generate.go │ │ │ │ ├── go_module_metadata.go │ │ │ │ ├── load_options.go │ │ │ │ ├── local.go │ │ │ │ ├── provider.go │ │ │ │ ├── resolve.go │ │ │ │ ├── resolve_bearer_token.go │ │ │ │ ├── resolve_credentials.go │ │ │ │ └── shared_config.go │ │ │ ├── credentials/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── doc.go │ │ │ │ ├── ec2rolecreds/ │ │ │ │ │ ├── doc.go │ │ │ │ │ └── provider.go │ │ │ │ ├── endpointcreds/ │ │ │ │ │ ├── internal/ │ │ │ │ │ │ └── client/ │ │ │ │ │ │ ├── auth.go │ │ │ │ │ │ ├── client.go │ │ │ │ │ │ ├── endpoints.go │ │ │ │ │ │ └── middleware.go │ │ │ │ │ └── provider.go │ │ │ │ ├── go_module_metadata.go │ │ │ │ ├── processcreds/ │ │ │ │ │ ├── doc.go │ │ │ │ │ └── provider.go │ │ │ │ ├── ssocreds/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── sso_cached_token.go │ │ │ │ │ ├── sso_credentials_provider.go │ │ │ │ │ └── sso_token_provider.go │ │ │ │ ├── static_provider.go │ │ │ │ └── stscreds/ │ │ │ │ ├── assume_role_provider.go │ │ │ │ └── web_identity_provider.go │ │ │ ├── feature/ │ │ │ │ └── ec2/ │ │ │ │ └── imds/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── api_client.go │ │ │ │ ├── api_op_GetDynamicData.go │ │ │ │ ├── api_op_GetIAMInfo.go │ │ │ │ ├── api_op_GetInstanceIdentityDocument.go │ │ │ │ ├── api_op_GetMetadata.go │ │ │ │ ├── api_op_GetRegion.go │ │ │ │ ├── api_op_GetToken.go │ │ │ │ ├── api_op_GetUserData.go │ │ │ │ ├── auth.go │ │ │ │ ├── doc.go │ │ │ │ ├── endpoints.go │ │ │ │ ├── go_module_metadata.go │ │ │ │ ├── internal/ │ │ │ │ │ └── config/ │ │ │ │ │ └── resolvers.go │ │ │ │ ├── request_middleware.go │ │ │ │ └── token_provider.go │ │ │ ├── internal/ │ │ │ │ ├── auth/ │ │ │ │ │ ├── auth.go │ │ │ │ │ ├── scheme.go │ │ │ │ │ └── smithy/ │ │ │ │ │ ├── bearer_token_adapter.go │ │ │ │ │ ├── bearer_token_signer_adapter.go │ │ │ │ │ ├── credentials_adapter.go │ │ │ │ │ ├── smithy.go │ │ │ │ │ └── v4signer_adapter.go │ │ │ │ ├── configsources/ │ │ │ │ │ ├── CHANGELOG.md │ │ │ │ │ ├── LICENSE.txt │ │ │ │ │ ├── config.go │ │ │ │ │ ├── endpoints.go │ │ │ │ │ └── go_module_metadata.go │ │ │ │ ├── context/ │ │ │ │ │ └── context.go │ │ │ │ ├── endpoints/ │ │ │ │ │ ├── awsrulesfn/ │ │ │ │ │ │ ├── arn.go │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── generate.go │ │ │ │ │ │ ├── host.go │ │ │ │ │ │ ├── partition.go │ │ │ │ │ │ ├── partitions.go │ │ │ │ │ │ └── partitions.json │ │ │ │ │ ├── endpoints.go │ │ │ │ │ └── v2/ │ │ │ │ │ ├── CHANGELOG.md │ │ │ │ │ ├── LICENSE.txt │ │ │ │ │ ├── endpoints.go │ │ │ │ │ └── go_module_metadata.go │ │ │ │ ├── ini/ │ │ │ │ │ ├── CHANGELOG.md │ │ │ │ │ ├── LICENSE.txt │ │ │ │ │ ├── errors.go │ │ │ │ │ ├── go_module_metadata.go │ │ │ │ │ ├── ini.go │ │ │ │ │ ├── parse.go │ │ │ │ │ ├── sections.go │ │ │ │ │ ├── strings.go │ │ │ │ │ ├── token.go │ │ │ │ │ ├── tokenize.go │ │ │ │ │ └── value.go │ │ │ │ ├── middleware/ │ │ │ │ │ └── middleware.go │ │ │ │ ├── rand/ │ │ │ │ │ └── rand.go │ │ │ │ ├── sdk/ │ │ │ │ │ ├── interfaces.go │ │ │ │ │ └── time.go │ │ │ │ ├── sdkio/ │ │ │ │ │ └── byte.go │ │ │ │ ├── shareddefaults/ │ │ │ │ │ └── shared_config.go │ │ │ │ ├── strings/ │ │ │ │ │ └── strings.go │ │ │ │ ├── sync/ │ │ │ │ │ └── singleflight/ │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── docs.go │ │ │ │ │ └── singleflight.go │ │ │ │ └── timeconv/ │ │ │ │ └── duration.go │ │ │ └── service/ │ │ │ ├── ecr/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── api_client.go │ │ │ │ ├── api_op_BatchCheckLayerAvailability.go │ │ │ │ ├── api_op_BatchDeleteImage.go │ │ │ │ ├── api_op_BatchGetImage.go │ │ │ │ ├── api_op_BatchGetRepositoryScanningConfiguration.go │ │ │ │ ├── api_op_CompleteLayerUpload.go │ │ │ │ ├── api_op_CreatePullThroughCacheRule.go │ │ │ │ ├── api_op_CreateRepository.go │ │ │ │ ├── api_op_DeleteLifecyclePolicy.go │ │ │ │ ├── api_op_DeletePullThroughCacheRule.go │ │ │ │ ├── api_op_DeleteRegistryPolicy.go │ │ │ │ ├── api_op_DeleteRepository.go │ │ │ │ ├── api_op_DeleteRepositoryPolicy.go │ │ │ │ ├── api_op_DescribeImageReplicationStatus.go │ │ │ │ ├── api_op_DescribeImageScanFindings.go │ │ │ │ ├── api_op_DescribeImages.go │ │ │ │ ├── api_op_DescribePullThroughCacheRules.go │ │ │ │ ├── api_op_DescribeRegistry.go │ │ │ │ ├── api_op_DescribeRepositories.go │ │ │ │ ├── api_op_GetAuthorizationToken.go │ │ │ │ ├── api_op_GetDownloadUrlForLayer.go │ │ │ │ ├── api_op_GetLifecyclePolicy.go │ │ │ │ ├── api_op_GetLifecyclePolicyPreview.go │ │ │ │ ├── api_op_GetRegistryPolicy.go │ │ │ │ ├── api_op_GetRegistryScanningConfiguration.go │ │ │ │ ├── api_op_GetRepositoryPolicy.go │ │ │ │ ├── api_op_InitiateLayerUpload.go │ │ │ │ ├── api_op_ListImages.go │ │ │ │ ├── api_op_ListTagsForResource.go │ │ │ │ ├── api_op_PutImage.go │ │ │ │ ├── api_op_PutImageScanningConfiguration.go │ │ │ │ ├── api_op_PutImageTagMutability.go │ │ │ │ ├── api_op_PutLifecyclePolicy.go │ │ │ │ ├── api_op_PutRegistryPolicy.go │ │ │ │ ├── api_op_PutRegistryScanningConfiguration.go │ │ │ │ ├── api_op_PutReplicationConfiguration.go │ │ │ │ ├── api_op_SetRepositoryPolicy.go │ │ │ │ ├── api_op_StartImageScan.go │ │ │ │ ├── api_op_StartLifecyclePolicyPreview.go │ │ │ │ ├── api_op_TagResource.go │ │ │ │ ├── api_op_UntagResource.go │ │ │ │ ├── api_op_UploadLayerPart.go │ │ │ │ ├── deserializers.go │ │ │ │ ├── doc.go │ │ │ │ ├── endpoints.go │ │ │ │ ├── generated.json │ │ │ │ ├── go_module_metadata.go │ │ │ │ ├── internal/ │ │ │ │ │ └── endpoints/ │ │ │ │ │ └── endpoints.go │ │ │ │ ├── serializers.go │ │ │ │ ├── types/ │ │ │ │ │ ├── enums.go │ │ │ │ │ ├── errors.go │ │ │ │ │ └── types.go │ │ │ │ └── validators.go │ │ │ ├── ecrpublic/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── api_client.go │ │ │ │ ├── api_op_BatchCheckLayerAvailability.go │ │ │ │ ├── api_op_BatchDeleteImage.go │ │ │ │ ├── api_op_CompleteLayerUpload.go │ │ │ │ ├── api_op_CreateRepository.go │ │ │ │ ├── api_op_DeleteRepository.go │ │ │ │ ├── api_op_DeleteRepositoryPolicy.go │ │ │ │ ├── api_op_DescribeImageTags.go │ │ │ │ ├── api_op_DescribeImages.go │ │ │ │ ├── api_op_DescribeRegistries.go │ │ │ │ ├── api_op_DescribeRepositories.go │ │ │ │ ├── api_op_GetAuthorizationToken.go │ │ │ │ ├── api_op_GetRegistryCatalogData.go │ │ │ │ ├── api_op_GetRepositoryCatalogData.go │ │ │ │ ├── api_op_GetRepositoryPolicy.go │ │ │ │ ├── api_op_InitiateLayerUpload.go │ │ │ │ ├── api_op_ListTagsForResource.go │ │ │ │ ├── api_op_PutImage.go │ │ │ │ ├── api_op_PutRegistryCatalogData.go │ │ │ │ ├── api_op_PutRepositoryCatalogData.go │ │ │ │ ├── api_op_SetRepositoryPolicy.go │ │ │ │ ├── api_op_TagResource.go │ │ │ │ ├── api_op_UntagResource.go │ │ │ │ ├── api_op_UploadLayerPart.go │ │ │ │ ├── deserializers.go │ │ │ │ ├── doc.go │ │ │ │ ├── endpoints.go │ │ │ │ ├── generated.json │ │ │ │ ├── go_module_metadata.go │ │ │ │ ├── internal/ │ │ │ │ │ └── endpoints/ │ │ │ │ │ └── endpoints.go │ │ │ │ ├── serializers.go │ │ │ │ ├── types/ │ │ │ │ │ ├── enums.go │ │ │ │ │ ├── errors.go │ │ │ │ │ └── types.go │ │ │ │ └── validators.go │ │ │ ├── internal/ │ │ │ │ ├── accept-encoding/ │ │ │ │ │ ├── CHANGELOG.md │ │ │ │ │ ├── LICENSE.txt │ │ │ │ │ ├── accept_encoding_gzip.go │ │ │ │ │ ├── doc.go │ │ │ │ │ └── go_module_metadata.go │ │ │ │ └── presigned-url/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── context.go │ │ │ │ ├── doc.go │ │ │ │ ├── go_module_metadata.go │ │ │ │ └── middleware.go │ │ │ ├── ssm/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── api_client.go │ │ │ │ ├── api_op_AddTagsToResource.go │ │ │ │ ├── api_op_AssociateOpsItemRelatedItem.go │ │ │ │ ├── api_op_CancelCommand.go │ │ │ │ ├── api_op_CancelMaintenanceWindowExecution.go │ │ │ │ ├── api_op_CreateActivation.go │ │ │ │ ├── api_op_CreateAssociation.go │ │ │ │ ├── api_op_CreateAssociationBatch.go │ │ │ │ ├── api_op_CreateDocument.go │ │ │ │ ├── api_op_CreateMaintenanceWindow.go │ │ │ │ ├── api_op_CreateOpsItem.go │ │ │ │ ├── api_op_CreateOpsMetadata.go │ │ │ │ ├── api_op_CreatePatchBaseline.go │ │ │ │ ├── api_op_CreateResourceDataSync.go │ │ │ │ ├── api_op_DeleteActivation.go │ │ │ │ ├── api_op_DeleteAssociation.go │ │ │ │ ├── api_op_DeleteDocument.go │ │ │ │ ├── api_op_DeleteInventory.go │ │ │ │ ├── api_op_DeleteMaintenanceWindow.go │ │ │ │ ├── api_op_DeleteOpsItem.go │ │ │ │ ├── api_op_DeleteOpsMetadata.go │ │ │ │ ├── api_op_DeleteParameter.go │ │ │ │ ├── api_op_DeleteParameters.go │ │ │ │ ├── api_op_DeletePatchBaseline.go │ │ │ │ ├── api_op_DeleteResourceDataSync.go │ │ │ │ ├── api_op_DeleteResourcePolicy.go │ │ │ │ ├── api_op_DeregisterManagedInstance.go │ │ │ │ ├── api_op_DeregisterPatchBaselineForPatchGroup.go │ │ │ │ ├── api_op_DeregisterTargetFromMaintenanceWindow.go │ │ │ │ ├── api_op_DeregisterTaskFromMaintenanceWindow.go │ │ │ │ ├── api_op_DescribeActivations.go │ │ │ │ ├── api_op_DescribeAssociation.go │ │ │ │ ├── api_op_DescribeAssociationExecutionTargets.go │ │ │ │ ├── api_op_DescribeAssociationExecutions.go │ │ │ │ ├── api_op_DescribeAutomationExecutions.go │ │ │ │ ├── api_op_DescribeAutomationStepExecutions.go │ │ │ │ ├── api_op_DescribeAvailablePatches.go │ │ │ │ ├── api_op_DescribeDocument.go │ │ │ │ ├── api_op_DescribeDocumentPermission.go │ │ │ │ ├── api_op_DescribeEffectiveInstanceAssociations.go │ │ │ │ ├── api_op_DescribeEffectivePatchesForPatchBaseline.go │ │ │ │ ├── api_op_DescribeInstanceAssociationsStatus.go │ │ │ │ ├── api_op_DescribeInstanceInformation.go │ │ │ │ ├── api_op_DescribeInstancePatchStates.go │ │ │ │ ├── api_op_DescribeInstancePatchStatesForPatchGroup.go │ │ │ │ ├── api_op_DescribeInstancePatches.go │ │ │ │ ├── api_op_DescribeInventoryDeletions.go │ │ │ │ ├── api_op_DescribeMaintenanceWindowExecutionTaskInvocations.go │ │ │ │ ├── api_op_DescribeMaintenanceWindowExecutionTasks.go │ │ │ │ ├── api_op_DescribeMaintenanceWindowExecutions.go │ │ │ │ ├── api_op_DescribeMaintenanceWindowSchedule.go │ │ │ │ ├── api_op_DescribeMaintenanceWindowTargets.go │ │ │ │ ├── api_op_DescribeMaintenanceWindowTasks.go │ │ │ │ ├── api_op_DescribeMaintenanceWindows.go │ │ │ │ ├── api_op_DescribeMaintenanceWindowsForTarget.go │ │ │ │ ├── api_op_DescribeOpsItems.go │ │ │ │ ├── api_op_DescribeParameters.go │ │ │ │ ├── api_op_DescribePatchBaselines.go │ │ │ │ ├── api_op_DescribePatchGroupState.go │ │ │ │ ├── api_op_DescribePatchGroups.go │ │ │ │ ├── api_op_DescribePatchProperties.go │ │ │ │ ├── api_op_DescribeSessions.go │ │ │ │ ├── api_op_DisassociateOpsItemRelatedItem.go │ │ │ │ ├── api_op_GetAutomationExecution.go │ │ │ │ ├── api_op_GetCalendarState.go │ │ │ │ ├── api_op_GetCommandInvocation.go │ │ │ │ ├── api_op_GetConnectionStatus.go │ │ │ │ ├── api_op_GetDefaultPatchBaseline.go │ │ │ │ ├── api_op_GetDeployablePatchSnapshotForInstance.go │ │ │ │ ├── api_op_GetDocument.go │ │ │ │ ├── api_op_GetInventory.go │ │ │ │ ├── api_op_GetInventorySchema.go │ │ │ │ ├── api_op_GetMaintenanceWindow.go │ │ │ │ ├── api_op_GetMaintenanceWindowExecution.go │ │ │ │ ├── api_op_GetMaintenanceWindowExecutionTask.go │ │ │ │ ├── api_op_GetMaintenanceWindowExecutionTaskInvocation.go │ │ │ │ ├── api_op_GetMaintenanceWindowTask.go │ │ │ │ ├── api_op_GetOpsItem.go │ │ │ │ ├── api_op_GetOpsMetadata.go │ │ │ │ ├── api_op_GetOpsSummary.go │ │ │ │ ├── api_op_GetParameter.go │ │ │ │ ├── api_op_GetParameterHistory.go │ │ │ │ ├── api_op_GetParameters.go │ │ │ │ ├── api_op_GetParametersByPath.go │ │ │ │ ├── api_op_GetPatchBaseline.go │ │ │ │ ├── api_op_GetPatchBaselineForPatchGroup.go │ │ │ │ ├── api_op_GetResourcePolicies.go │ │ │ │ ├── api_op_GetServiceSetting.go │ │ │ │ ├── api_op_LabelParameterVersion.go │ │ │ │ ├── api_op_ListAssociationVersions.go │ │ │ │ ├── api_op_ListAssociations.go │ │ │ │ ├── api_op_ListCommandInvocations.go │ │ │ │ ├── api_op_ListCommands.go │ │ │ │ ├── api_op_ListComplianceItems.go │ │ │ │ ├── api_op_ListComplianceSummaries.go │ │ │ │ ├── api_op_ListDocumentMetadataHistory.go │ │ │ │ ├── api_op_ListDocumentVersions.go │ │ │ │ ├── api_op_ListDocuments.go │ │ │ │ ├── api_op_ListInventoryEntries.go │ │ │ │ ├── api_op_ListOpsItemEvents.go │ │ │ │ ├── api_op_ListOpsItemRelatedItems.go │ │ │ │ ├── api_op_ListOpsMetadata.go │ │ │ │ ├── api_op_ListResourceComplianceSummaries.go │ │ │ │ ├── api_op_ListResourceDataSync.go │ │ │ │ ├── api_op_ListTagsForResource.go │ │ │ │ ├── api_op_ModifyDocumentPermission.go │ │ │ │ ├── api_op_PutComplianceItems.go │ │ │ │ ├── api_op_PutInventory.go │ │ │ │ ├── api_op_PutParameter.go │ │ │ │ ├── api_op_PutResourcePolicy.go │ │ │ │ ├── api_op_RegisterDefaultPatchBaseline.go │ │ │ │ ├── api_op_RegisterPatchBaselineForPatchGroup.go │ │ │ │ ├── api_op_RegisterTargetWithMaintenanceWindow.go │ │ │ │ ├── api_op_RegisterTaskWithMaintenanceWindow.go │ │ │ │ ├── api_op_RemoveTagsFromResource.go │ │ │ │ ├── api_op_ResetServiceSetting.go │ │ │ │ ├── api_op_ResumeSession.go │ │ │ │ ├── api_op_SendAutomationSignal.go │ │ │ │ ├── api_op_SendCommand.go │ │ │ │ ├── api_op_StartAssociationsOnce.go │ │ │ │ ├── api_op_StartAutomationExecution.go │ │ │ │ ├── api_op_StartChangeRequestExecution.go │ │ │ │ ├── api_op_StartSession.go │ │ │ │ ├── api_op_StopAutomationExecution.go │ │ │ │ ├── api_op_TerminateSession.go │ │ │ │ ├── api_op_UnlabelParameterVersion.go │ │ │ │ ├── api_op_UpdateAssociation.go │ │ │ │ ├── api_op_UpdateAssociationStatus.go │ │ │ │ ├── api_op_UpdateDocument.go │ │ │ │ ├── api_op_UpdateDocumentDefaultVersion.go │ │ │ │ ├── api_op_UpdateDocumentMetadata.go │ │ │ │ ├── api_op_UpdateMaintenanceWindow.go │ │ │ │ ├── api_op_UpdateMaintenanceWindowTarget.go │ │ │ │ ├── api_op_UpdateMaintenanceWindowTask.go │ │ │ │ ├── api_op_UpdateManagedInstanceRole.go │ │ │ │ ├── api_op_UpdateOpsItem.go │ │ │ │ ├── api_op_UpdateOpsMetadata.go │ │ │ │ ├── api_op_UpdatePatchBaseline.go │ │ │ │ ├── api_op_UpdateResourceDataSync.go │ │ │ │ ├── api_op_UpdateServiceSetting.go │ │ │ │ ├── auth.go │ │ │ │ ├── deserializers.go │ │ │ │ ├── doc.go │ │ │ │ ├── endpoints.go │ │ │ │ ├── generated.json │ │ │ │ ├── go_module_metadata.go │ │ │ │ ├── internal/ │ │ │ │ │ └── endpoints/ │ │ │ │ │ └── endpoints.go │ │ │ │ ├── options.go │ │ │ │ ├── serializers.go │ │ │ │ ├── types/ │ │ │ │ │ ├── enums.go │ │ │ │ │ ├── errors.go │ │ │ │ │ └── types.go │ │ │ │ └── validators.go │ │ │ ├── sso/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── api_client.go │ │ │ │ ├── api_op_GetRoleCredentials.go │ │ │ │ ├── api_op_ListAccountRoles.go │ │ │ │ ├── api_op_ListAccounts.go │ │ │ │ ├── api_op_Logout.go │ │ │ │ ├── auth.go │ │ │ │ ├── deserializers.go │ │ │ │ ├── doc.go │ │ │ │ ├── endpoints.go │ │ │ │ ├── generated.json │ │ │ │ ├── go_module_metadata.go │ │ │ │ ├── internal/ │ │ │ │ │ └── endpoints/ │ │ │ │ │ └── endpoints.go │ │ │ │ ├── options.go │ │ │ │ ├── serializers.go │ │ │ │ ├── types/ │ │ │ │ │ ├── errors.go │ │ │ │ │ └── types.go │ │ │ │ └── validators.go │ │ │ ├── ssooidc/ │ │ │ │ ├── CHANGELOG.md │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── api_client.go │ │ │ │ ├── api_op_CreateToken.go │ │ │ │ ├── api_op_CreateTokenWithIAM.go │ │ │ │ ├── api_op_RegisterClient.go │ │ │ │ ├── api_op_StartDeviceAuthorization.go │ │ │ │ ├── auth.go │ │ │ │ ├── deserializers.go │ │ │ │ ├── doc.go │ │ │ │ ├── endpoints.go │ │ │ │ ├── generated.json │ │ │ │ ├── go_module_metadata.go │ │ │ │ ├── internal/ │ │ │ │ │ └── endpoints/ │ │ │ │ │ └── endpoints.go │ │ │ │ ├── options.go │ │ │ │ ├── serializers.go │ │ │ │ ├── types/ │ │ │ │ │ ├── errors.go │ │ │ │ │ └── types.go │ │ │ │ └── validators.go │ │ │ └── sts/ │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE.txt │ │ │ ├── api_client.go │ │ │ ├── api_op_AssumeRole.go │ │ │ ├── api_op_AssumeRoleWithSAML.go │ │ │ ├── api_op_AssumeRoleWithWebIdentity.go │ │ │ ├── api_op_DecodeAuthorizationMessage.go │ │ │ ├── api_op_GetAccessKeyInfo.go │ │ │ ├── api_op_GetCallerIdentity.go │ │ │ ├── api_op_GetFederationToken.go │ │ │ ├── api_op_GetSessionToken.go │ │ │ ├── auth.go │ │ │ ├── deserializers.go │ │ │ ├── doc.go │ │ │ ├── endpoints.go │ │ │ ├── generated.json │ │ │ ├── go_module_metadata.go │ │ │ ├── internal/ │ │ │ │ └── endpoints/ │ │ │ │ └── endpoints.go │ │ │ ├── options.go │ │ │ ├── serializers.go │ │ │ ├── types/ │ │ │ │ ├── errors.go │ │ │ │ └── types.go │ │ │ └── validators.go │ │ └── smithy-go/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── CHANGELOG.md │ │ ├── CODE_OF_CONDUCT.md │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── NOTICE │ │ ├── README.md │ │ ├── auth/ │ │ │ ├── auth.go │ │ │ ├── bearer/ │ │ │ │ ├── docs.go │ │ │ │ ├── middleware.go │ │ │ │ ├── token.go │ │ │ │ └── token_cache.go │ │ │ ├── identity.go │ │ │ ├── option.go │ │ │ └── scheme_id.go │ │ ├── context/ │ │ │ └── suppress_expired.go │ │ ├── doc.go │ │ ├── document/ │ │ │ ├── doc.go │ │ │ ├── document.go │ │ │ └── errors.go │ │ ├── document.go │ │ ├── encoding/ │ │ │ ├── doc.go │ │ │ ├── encoding.go │ │ │ ├── httpbinding/ │ │ │ │ ├── encode.go │ │ │ │ ├── header.go │ │ │ │ ├── path_replace.go │ │ │ │ ├── query.go │ │ │ │ └── uri.go │ │ │ ├── json/ │ │ │ │ ├── array.go │ │ │ │ ├── constants.go │ │ │ │ ├── decoder_util.go │ │ │ │ ├── encoder.go │ │ │ │ ├── escape.go │ │ │ │ ├── object.go │ │ │ │ └── value.go │ │ │ └── xml/ │ │ │ ├── array.go │ │ │ ├── constants.go │ │ │ ├── doc.go │ │ │ ├── element.go │ │ │ ├── encoder.go │ │ │ ├── error_utils.go │ │ │ ├── escape.go │ │ │ ├── map.go │ │ │ ├── value.go │ │ │ └── xml_decoder.go │ │ ├── endpoints/ │ │ │ └── endpoint.go │ │ ├── errors.go │ │ ├── go_module_metadata.go │ │ ├── internal/ │ │ │ └── sync/ │ │ │ └── singleflight/ │ │ │ ├── LICENSE │ │ │ ├── docs.go │ │ │ └── singleflight.go │ │ ├── io/ │ │ │ ├── byte.go │ │ │ ├── doc.go │ │ │ ├── reader.go │ │ │ └── ringbuffer.go │ │ ├── local-mod-replace.sh │ │ ├── logging/ │ │ │ └── logger.go │ │ ├── middleware/ │ │ │ ├── doc.go │ │ │ ├── logging.go │ │ │ ├── metadata.go │ │ │ ├── middleware.go │ │ │ ├── ordered_group.go │ │ │ ├── stack.go │ │ │ ├── stack_values.go │ │ │ ├── step_build.go │ │ │ ├── step_deserialize.go │ │ │ ├── step_finalize.go │ │ │ ├── step_initialize.go │ │ │ └── step_serialize.go │ │ ├── modman.toml │ │ ├── private/ │ │ │ └── requestcompression/ │ │ │ ├── gzip.go │ │ │ ├── middleware_capture_request_compression.go │ │ │ └── request_compression.go │ │ ├── properties.go │ │ ├── ptr/ │ │ │ ├── doc.go │ │ │ ├── from_ptr.go │ │ │ ├── gen_scalars.go │ │ │ └── to_ptr.go │ │ ├── rand/ │ │ │ ├── doc.go │ │ │ ├── rand.go │ │ │ └── uuid.go │ │ ├── time/ │ │ │ └── time.go │ │ ├── transport/ │ │ │ └── http/ │ │ │ ├── auth.go │ │ │ ├── auth_schemes.go │ │ │ ├── checksum_middleware.go │ │ │ ├── client.go │ │ │ ├── doc.go │ │ │ ├── headerlist.go │ │ │ ├── host.go │ │ │ ├── internal/ │ │ │ │ └── io/ │ │ │ │ └── safe.go │ │ │ ├── md5_checksum.go │ │ │ ├── middleware_close_response_body.go │ │ │ ├── middleware_content_length.go │ │ │ ├── middleware_header_comment.go │ │ │ ├── middleware_headers.go │ │ │ ├── middleware_http_logging.go │ │ │ ├── middleware_metadata.go │ │ │ ├── middleware_min_proto.go │ │ │ ├── properties.go │ │ │ ├── request.go │ │ │ ├── response.go │ │ │ ├── time.go │ │ │ ├── url.go │ │ │ └── user_agent.go │ │ ├── validation.go │ │ └── waiter/ │ │ ├── logger.go │ │ └── waiter.go │ ├── awslabs/ │ │ └── amazon-ecr-credential-helper/ │ │ └── ecr-login/ │ │ ├── LICENSE │ │ ├── api/ │ │ │ ├── client.go │ │ │ └── factory.go │ │ ├── cache/ │ │ │ ├── build.go │ │ │ ├── credentials.go │ │ │ ├── file.go │ │ │ └── null.go │ │ ├── config/ │ │ │ ├── cache_dir.go │ │ │ └── log.go │ │ ├── ecr.go │ │ └── version/ │ │ └── version.go │ ├── aymanbagabas/ │ │ └── go-osc52/ │ │ └── v2/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── osc52.go │ ├── beorn7/ │ │ └── perks/ │ │ ├── LICENSE │ │ └── quantile/ │ │ ├── exampledata.txt │ │ └── stream.go │ ├── bits-and-blooms/ │ │ └── bitset/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── azure-pipelines.yml │ │ ├── bitset.go │ │ ├── popcnt.go │ │ ├── popcnt_19.go │ │ ├── popcnt_amd64.go │ │ ├── popcnt_amd64.s │ │ ├── popcnt_generic.go │ │ ├── select.go │ │ ├── trailing_zeros_18.go │ │ └── trailing_zeros_19.go │ ├── blang/ │ │ └── semver/ │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── json.go │ │ ├── package.json │ │ ├── range.go │ │ ├── semver.go │ │ ├── sort.go │ │ ├── sql.go │ │ └── v4/ │ │ ├── LICENSE │ │ ├── json.go │ │ ├── range.go │ │ ├── semver.go │ │ ├── sort.go │ │ └── sql.go │ ├── bmatcuk/ │ │ └── doublestar/ │ │ └── v4/ │ │ ├── .codecov.yml │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── UPGRADING.md │ │ ├── doublestar.go │ │ ├── glob.go │ │ ├── globoptions.go │ │ ├── globwalk.go │ │ ├── match.go │ │ ├── utils.go │ │ └── validate.go │ ├── catppuccin/ │ │ └── go/ │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── .goreleaser.yaml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── frappe.go │ │ ├── latte.go │ │ ├── macchiato.go │ │ ├── main.go │ │ └── mocha.go │ ├── cenkalti/ │ │ └── backoff/ │ │ └── v4/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── backoff.go │ │ ├── context.go │ │ ├── exponential.go │ │ ├── retry.go │ │ ├── ticker.go │ │ ├── timer.go │ │ └── tries.go │ ├── cespare/ │ │ └── xxhash/ │ │ └── v2/ │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── testall.sh │ │ ├── xxhash.go │ │ ├── xxhash_amd64.s │ │ ├── xxhash_arm64.s │ │ ├── xxhash_asm.go │ │ ├── xxhash_other.go │ │ ├── xxhash_safe.go │ │ └── xxhash_unsafe.go │ ├── charmbracelet/ │ │ ├── bubbles/ │ │ │ ├── LICENSE │ │ │ ├── cursor/ │ │ │ │ └── cursor.go │ │ │ ├── filepicker/ │ │ │ │ ├── filepicker.go │ │ │ │ ├── hidden_unix.go │ │ │ │ └── hidden_windows.go │ │ │ ├── help/ │ │ │ │ └── help.go │ │ │ ├── key/ │ │ │ │ └── key.go │ │ │ ├── runeutil/ │ │ │ │ └── runeutil.go │ │ │ ├── spinner/ │ │ │ │ └── spinner.go │ │ │ ├── textarea/ │ │ │ │ ├── memoization/ │ │ │ │ │ └── memoization.go │ │ │ │ └── textarea.go │ │ │ ├── textinput/ │ │ │ │ └── textinput.go │ │ │ └── viewport/ │ │ │ ├── keymap.go │ │ │ └── viewport.go │ │ ├── bubbletea/ │ │ │ ├── .gitattributes │ │ │ ├── .gitignore │ │ │ ├── .golangci-soft.yml │ │ │ ├── .golangci.yml │ │ │ ├── .goreleaser.yml │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── commands.go │ │ │ ├── exec.go │ │ │ ├── focus.go │ │ │ ├── inputreader_other.go │ │ │ ├── inputreader_windows.go │ │ │ ├── key.go │ │ │ ├── key_other.go │ │ │ ├── key_sequences.go │ │ │ ├── key_windows.go │ │ │ ├── logging.go │ │ │ ├── mouse.go │ │ │ ├── nil_renderer.go │ │ │ ├── options.go │ │ │ ├── renderer.go │ │ │ ├── screen.go │ │ │ ├── signals_unix.go │ │ │ ├── signals_windows.go │ │ │ ├── standard_renderer.go │ │ │ ├── tea.go │ │ │ ├── tea_init.go │ │ │ ├── tty.go │ │ │ ├── tty_unix.go │ │ │ └── tty_windows.go │ │ ├── huh/ │ │ │ ├── .gitattributes │ │ │ ├── .gitignore │ │ │ ├── .golangci-soft.yml │ │ │ ├── .golangci.yml │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── SECURITY.md │ │ │ ├── accessibility/ │ │ │ │ └── accessibility.go │ │ │ ├── accessor.go │ │ │ ├── clamp.go │ │ │ ├── eval.go │ │ │ ├── field_confirm.go │ │ │ ├── field_filepicker.go │ │ │ ├── field_input.go │ │ │ ├── field_multiselect.go │ │ │ ├── field_note.go │ │ │ ├── field_select.go │ │ │ ├── field_text.go │ │ │ ├── form.go │ │ │ ├── group.go │ │ │ ├── internal/ │ │ │ │ └── selector/ │ │ │ │ └── selector.go │ │ │ ├── keymap.go │ │ │ ├── layout.go │ │ │ ├── option.go │ │ │ ├── run.go │ │ │ ├── theme.go │ │ │ └── validate.go │ │ ├── lipgloss/ │ │ │ ├── .gitignore │ │ │ ├── .golangci-soft.yml │ │ │ ├── .golangci.yml │ │ │ ├── .goreleaser.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── align.go │ │ │ ├── ansi_unix.go │ │ │ ├── ansi_windows.go │ │ │ ├── borders.go │ │ │ ├── color.go │ │ │ ├── get.go │ │ │ ├── join.go │ │ │ ├── position.go │ │ │ ├── renderer.go │ │ │ ├── runes.go │ │ │ ├── set.go │ │ │ ├── size.go │ │ │ ├── style.go │ │ │ ├── unset.go │ │ │ └── whitespace.go │ │ └── x/ │ │ ├── ansi/ │ │ │ ├── LICENSE │ │ │ ├── ansi.go │ │ │ ├── ascii.go │ │ │ ├── background.go │ │ │ ├── c0.go │ │ │ ├── c1.go │ │ │ ├── clipboard.go │ │ │ ├── color.go │ │ │ ├── csi.go │ │ │ ├── ctrl.go │ │ │ ├── cursor.go │ │ │ ├── dcs.go │ │ │ ├── doc.go │ │ │ ├── hyperlink.go │ │ │ ├── kitty.go │ │ │ ├── mode.go │ │ │ ├── osc.go │ │ │ ├── params.go │ │ │ ├── parser/ │ │ │ │ ├── const.go │ │ │ │ ├── seq.go │ │ │ │ └── transition_table.go │ │ │ ├── parser.go │ │ │ ├── parser_decode.go │ │ │ ├── passthrough.go │ │ │ ├── screen.go │ │ │ ├── sequence.go │ │ │ ├── style.go │ │ │ ├── termcap.go │ │ │ ├── title.go │ │ │ ├── truncate.go │ │ │ ├── util.go │ │ │ ├── width.go │ │ │ ├── wrap.go │ │ │ └── xterm.go │ │ ├── exp/ │ │ │ └── strings/ │ │ │ ├── LICENSE │ │ │ └── join.go │ │ └── term/ │ │ ├── LICENSE │ │ ├── term.go │ │ ├── term_other.go │ │ ├── term_unix.go │ │ ├── term_unix_bsd.go │ │ ├── term_unix_other.go │ │ ├── term_windows.go │ │ ├── terminal.go │ │ └── util.go │ ├── chrismellard/ │ │ └── docker-credential-acr-env/ │ │ ├── LICENSE │ │ └── pkg/ │ │ ├── credhelper/ │ │ │ └── helper.go │ │ ├── registry/ │ │ │ ├── const.go │ │ │ └── registry.go │ │ └── token/ │ │ └── token.go │ ├── coder/ │ │ └── websocket/ │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── accept.go │ │ ├── close.go │ │ ├── compress.go │ │ ├── conn.go │ │ ├── dial.go │ │ ├── doc.go │ │ ├── frame.go │ │ ├── internal/ │ │ │ ├── bpool/ │ │ │ │ └── bpool.go │ │ │ ├── errd/ │ │ │ │ └── wrap.go │ │ │ ├── util/ │ │ │ │ └── util.go │ │ │ ├── wsjs/ │ │ │ │ └── wsjs_js.go │ │ │ └── xsync/ │ │ │ ├── go.go │ │ │ └── int64.go │ │ ├── make.sh │ │ ├── mask.go │ │ ├── mask_amd64.s │ │ ├── mask_arm64.s │ │ ├── mask_asm.go │ │ ├── mask_go.go │ │ ├── netconn.go │ │ ├── netconn_js.go │ │ ├── netconn_notjs.go │ │ ├── read.go │ │ ├── stringer.go │ │ ├── write.go │ │ └── ws_js.go │ ├── compose-spec/ │ │ └── compose-go/ │ │ └── v2/ │ │ ├── LICENSE │ │ ├── NOTICE │ │ ├── cli/ │ │ │ └── options.go │ │ ├── consts/ │ │ │ └── consts.go │ │ ├── dotenv/ │ │ │ ├── LICENSE │ │ │ ├── env.go │ │ │ ├── godotenv.go │ │ │ └── parser.go │ │ ├── errdefs/ │ │ │ └── errors.go │ │ ├── format/ │ │ │ └── volume.go │ │ ├── graph/ │ │ │ ├── cycle.go │ │ │ ├── graph.go │ │ │ ├── services.go │ │ │ └── traversal.go │ │ ├── interpolation/ │ │ │ └── interpolation.go │ │ ├── loader/ │ │ │ ├── environment.go │ │ │ ├── example1.env │ │ │ ├── example2.env │ │ │ ├── extends.go │ │ │ ├── fix.go │ │ │ ├── full-example.yml │ │ │ ├── include.go │ │ │ ├── interpolate.go │ │ │ ├── loader.go │ │ │ ├── mapstructure.go │ │ │ ├── normalize.go │ │ │ ├── paths.go │ │ │ ├── reset.go │ │ │ └── validate.go │ │ ├── override/ │ │ │ ├── extends.go │ │ │ ├── merge.go │ │ │ └── uncity.go │ │ ├── paths/ │ │ │ ├── context.go │ │ │ ├── extends.go │ │ │ ├── home.go │ │ │ ├── resolve.go │ │ │ ├── unix.go │ │ │ └── windows_path.go │ │ ├── schema/ │ │ │ ├── compose-spec.json │ │ │ ├── schema.go │ │ │ └── using-variables.yaml │ │ ├── template/ │ │ │ ├── template.go │ │ │ └── variables.go │ │ ├── transform/ │ │ │ ├── build.go │ │ │ ├── canonical.go │ │ │ ├── defaults.go │ │ │ ├── dependson.go │ │ │ ├── device.go │ │ │ ├── envfile.go │ │ │ ├── extends.go │ │ │ ├── external.go │ │ │ ├── include.go │ │ │ ├── mapping.go │ │ │ ├── ports.go │ │ │ ├── secrets.go │ │ │ ├── services.go │ │ │ ├── ssh.go │ │ │ ├── ulimits.go │ │ │ └── volume.go │ │ ├── tree/ │ │ │ └── path.go │ │ ├── types/ │ │ │ ├── bytes.go │ │ │ ├── command.go │ │ │ ├── config.go │ │ │ ├── derived.gen.go │ │ │ ├── develop.go │ │ │ ├── device.go │ │ │ ├── duration.go │ │ │ ├── envfile.go │ │ │ ├── healthcheck.go │ │ │ ├── hostList.go │ │ │ ├── labels.go │ │ │ ├── mapping.go │ │ │ ├── options.go │ │ │ ├── project.go │ │ │ ├── services.go │ │ │ ├── ssh.go │ │ │ ├── stringOrList.go │ │ │ └── types.go │ │ ├── utils/ │ │ │ ├── collectionutils.go │ │ │ ├── pathutils.go │ │ │ ├── set.go │ │ │ └── stringutils.go │ │ └── validation/ │ │ ├── external.go │ │ ├── validation.go │ │ └── volume.go │ ├── containerd/ │ │ ├── console/ │ │ │ ├── .golangci.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── console.go │ │ │ ├── console_linux.go │ │ │ ├── console_other.go │ │ │ ├── console_unix.go │ │ │ ├── console_windows.go │ │ │ ├── pty_freebsd_cgo.go │ │ │ ├── pty_freebsd_nocgo.go │ │ │ ├── pty_unix.go │ │ │ ├── pty_zos.go │ │ │ ├── tc_darwin.go │ │ │ ├── tc_freebsd_cgo.go │ │ │ ├── tc_freebsd_nocgo.go │ │ │ ├── tc_linux.go │ │ │ ├── tc_netbsd.go │ │ │ ├── tc_openbsd_cgo.go │ │ │ ├── tc_openbsd_nocgo.go │ │ │ ├── tc_unix.go │ │ │ └── tc_zos.go │ │ ├── containerd/ │ │ │ ├── api/ │ │ │ │ ├── LICENSE │ │ │ │ └── services/ │ │ │ │ └── content/ │ │ │ │ └── v1/ │ │ │ │ ├── content.pb.go │ │ │ │ ├── content.proto │ │ │ │ ├── content_grpc.pb.go │ │ │ │ ├── content_ttrpc.pb.go │ │ │ │ └── doc.go │ │ │ └── v2/ │ │ │ ├── LICENSE │ │ │ ├── NOTICE │ │ │ ├── core/ │ │ │ │ ├── content/ │ │ │ │ │ ├── adaptor.go │ │ │ │ │ ├── content.go │ │ │ │ │ ├── helpers.go │ │ │ │ │ └── proxy/ │ │ │ │ │ ├── content_reader.go │ │ │ │ │ ├── content_store.go │ │ │ │ │ └── content_writer.go │ │ │ │ ├── images/ │ │ │ │ │ ├── annotations.go │ │ │ │ │ ├── diffid.go │ │ │ │ │ ├── handlers.go │ │ │ │ │ ├── image.go │ │ │ │ │ ├── importexport.go │ │ │ │ │ ├── labels.go │ │ │ │ │ └── mediatypes.go │ │ │ │ ├── leases/ │ │ │ │ │ ├── context.go │ │ │ │ │ ├── grpc.go │ │ │ │ │ ├── id.go │ │ │ │ │ └── lease.go │ │ │ │ └── remotes/ │ │ │ │ ├── docker/ │ │ │ │ │ ├── auth/ │ │ │ │ │ │ ├── fetch.go │ │ │ │ │ │ └── parse.go │ │ │ │ │ ├── authorizer.go │ │ │ │ │ ├── converter.go │ │ │ │ │ ├── converter_fuzz.go │ │ │ │ │ ├── errcode.go │ │ │ │ │ ├── errdesc.go │ │ │ │ │ ├── fetcher.go │ │ │ │ │ ├── fetcher_fuzz.go │ │ │ │ │ ├── handler.go │ │ │ │ │ ├── httpreadseeker.go │ │ │ │ │ ├── pusher.go │ │ │ │ │ ├── registry.go │ │ │ │ │ ├── resolver.go │ │ │ │ │ ├── resolver_unix.go │ │ │ │ │ ├── resolver_windows.go │ │ │ │ │ ├── schema1/ │ │ │ │ │ │ └── converter.go │ │ │ │ │ ├── scope.go │ │ │ │ │ └── status.go │ │ │ │ ├── errors/ │ │ │ │ │ └── errors.go │ │ │ │ ├── handlers.go │ │ │ │ └── resolver.go │ │ │ ├── defaults/ │ │ │ │ ├── defaults.go │ │ │ │ ├── defaults_darwin.go │ │ │ │ ├── defaults_differ_windows.go │ │ │ │ ├── defaults_freebsd.go │ │ │ │ ├── defaults_linux.go │ │ │ │ ├── defaults_snapshotter_linux.go │ │ │ │ ├── defaults_snapshotter_unix.go │ │ │ │ ├── defaults_snapshotter_windows.go │ │ │ │ ├── defaults_unix.go │ │ │ │ ├── defaults_windows.go │ │ │ │ └── doc.go │ │ │ ├── internal/ │ │ │ │ ├── fsverity/ │ │ │ │ │ ├── fsverity_linux.go │ │ │ │ │ └── fsverity_other.go │ │ │ │ └── randutil/ │ │ │ │ └── randutil.go │ │ │ ├── pkg/ │ │ │ │ ├── archive/ │ │ │ │ │ └── compression/ │ │ │ │ │ ├── compression.go │ │ │ │ │ └── compression_fuzzer.go │ │ │ │ ├── deprecation/ │ │ │ │ │ └── deprecation.go │ │ │ │ ├── filters/ │ │ │ │ │ ├── adaptor.go │ │ │ │ │ ├── filter.go │ │ │ │ │ ├── parser.go │ │ │ │ │ ├── quote.go │ │ │ │ │ └── scanner.go │ │ │ │ ├── identifiers/ │ │ │ │ │ └── validate.go │ │ │ │ ├── kernelversion/ │ │ │ │ │ └── kernel_linux.go │ │ │ │ ├── labels/ │ │ │ │ │ ├── labels.go │ │ │ │ │ └── validate.go │ │ │ │ ├── namespaces/ │ │ │ │ │ ├── context.go │ │ │ │ │ ├── grpc.go │ │ │ │ │ ├── store.go │ │ │ │ │ └── ttrpc.go │ │ │ │ ├── protobuf/ │ │ │ │ │ ├── compare.go │ │ │ │ │ ├── timestamp.go │ │ │ │ │ └── types/ │ │ │ │ │ └── types.go │ │ │ │ ├── reference/ │ │ │ │ │ └── reference.go │ │ │ │ └── tracing/ │ │ │ │ ├── helpers.go │ │ │ │ ├── log.go │ │ │ │ └── tracing.go │ │ │ ├── plugins/ │ │ │ │ ├── content/ │ │ │ │ │ └── local/ │ │ │ │ │ ├── content_local_fuzzer.go │ │ │ │ │ ├── locks.go │ │ │ │ │ ├── readerat.go │ │ │ │ │ ├── store.go │ │ │ │ │ ├── store_bsd.go │ │ │ │ │ ├── store_openbsd.go │ │ │ │ │ ├── store_unix.go │ │ │ │ │ ├── store_windows.go │ │ │ │ │ ├── test_helper.go │ │ │ │ │ └── writer.go │ │ │ │ └── services/ │ │ │ │ └── content/ │ │ │ │ └── contentserver/ │ │ │ │ └── contentserver.go │ │ │ └── version/ │ │ │ └── version.go │ │ ├── continuity/ │ │ │ ├── AUTHORS │ │ │ ├── LICENSE │ │ │ └── sysx/ │ │ │ ├── README.md │ │ │ ├── nodata_linux.go │ │ │ ├── nodata_solaris.go │ │ │ ├── nodata_unix.go │ │ │ ├── xattr.go │ │ │ └── xattr_unsupported.go │ │ ├── errdefs/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── errors.go │ │ │ ├── pkg/ │ │ │ │ ├── LICENSE │ │ │ │ ├── errgrpc/ │ │ │ │ │ └── grpc.go │ │ │ │ └── internal/ │ │ │ │ ├── cause/ │ │ │ │ │ └── cause.go │ │ │ │ └── types/ │ │ │ │ └── collapsible.go │ │ │ └── resolve.go │ │ ├── log/ │ │ │ ├── .golangci.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── context.go │ │ ├── platforms/ │ │ │ ├── .gitattributes │ │ │ ├── .golangci.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── compare.go │ │ │ ├── cpuinfo.go │ │ │ ├── cpuinfo_linux.go │ │ │ ├── cpuinfo_other.go │ │ │ ├── database.go │ │ │ ├── defaults.go │ │ │ ├── defaults_darwin.go │ │ │ ├── defaults_freebsd.go │ │ │ ├── defaults_unix.go │ │ │ ├── defaults_windows.go │ │ │ ├── errors.go │ │ │ ├── platform_windows_compat.go │ │ │ └── platforms.go │ │ ├── stargz-snapshotter/ │ │ │ └── estargz/ │ │ │ ├── LICENSE │ │ │ ├── build.go │ │ │ ├── errorutil/ │ │ │ │ └── errors.go │ │ │ ├── estargz.go │ │ │ ├── gzip.go │ │ │ ├── testutil.go │ │ │ └── types.go │ │ ├── ttrpc/ │ │ │ ├── .gitattributes │ │ │ ├── .gitignore │ │ │ ├── .golangci.yml │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── PROTOCOL.md │ │ │ ├── Protobuild.toml │ │ │ ├── README.md │ │ │ ├── channel.go │ │ │ ├── client.go │ │ │ ├── codec.go │ │ │ ├── config.go │ │ │ ├── doc.go │ │ │ ├── errors.go │ │ │ ├── handshake.go │ │ │ ├── interceptor.go │ │ │ ├── metadata.go │ │ │ ├── request.pb.go │ │ │ ├── request.proto │ │ │ ├── server.go │ │ │ ├── services.go │ │ │ ├── stream.go │ │ │ ├── stream_server.go │ │ │ ├── test.proto │ │ │ └── unixcreds_linux.go │ │ └── typeurl/ │ │ └── v2/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── doc.go │ │ ├── types.go │ │ └── types_gogo.go │ ├── containers/ │ │ ├── image/ │ │ │ └── v5/ │ │ │ ├── LICENSE │ │ │ ├── docker/ │ │ │ │ └── reference/ │ │ │ │ ├── README.md │ │ │ │ ├── helpers.go │ │ │ │ ├── normalize.go │ │ │ │ ├── reference.go │ │ │ │ ├── regexp-additions.go │ │ │ │ └── regexp.go │ │ │ ├── internal/ │ │ │ │ ├── multierr/ │ │ │ │ │ └── multierr.go │ │ │ │ ├── rootless/ │ │ │ │ │ └── rootless.go │ │ │ │ └── set/ │ │ │ │ └── set.go │ │ │ ├── pkg/ │ │ │ │ ├── compression/ │ │ │ │ │ ├── internal/ │ │ │ │ │ │ └── types.go │ │ │ │ │ └── types/ │ │ │ │ │ └── types.go │ │ │ │ ├── docker/ │ │ │ │ │ └── config/ │ │ │ │ │ └── config.go │ │ │ │ └── sysregistriesv2/ │ │ │ │ ├── paths_common.go │ │ │ │ ├── paths_freebsd.go │ │ │ │ ├── shortnames.go │ │ │ │ └── system_registries_v2.go │ │ │ └── types/ │ │ │ └── types.go │ │ └── storage/ │ │ ├── AUTHORS │ │ ├── LICENSE │ │ ├── NOTICE │ │ └── pkg/ │ │ ├── fileutils/ │ │ │ ├── exists_freebsd.go │ │ │ ├── exists_unix.go │ │ │ ├── exists_windows.go │ │ │ ├── fileutils.go │ │ │ ├── fileutils_darwin.go │ │ │ ├── fileutils_solaris.go │ │ │ ├── fileutils_unix.go │ │ │ └── fileutils_windows.go │ │ ├── homedir/ │ │ │ ├── homedir.go │ │ │ ├── homedir_unix.go │ │ │ └── homedir_windows.go │ │ ├── idtools/ │ │ │ ├── idtools.go │ │ │ ├── idtools_supported.go │ │ │ ├── idtools_unix.go │ │ │ ├── idtools_unsupported.go │ │ │ ├── idtools_windows.go │ │ │ ├── parser.go │ │ │ ├── usergroupadd_linux.go │ │ │ ├── usergroupadd_unsupported.go │ │ │ └── utils_unix.go │ │ ├── ioutils/ │ │ │ ├── buffer.go │ │ │ ├── bytespipe.go │ │ │ ├── fswriters.go │ │ │ ├── fswriters_linux.go │ │ │ ├── fswriters_other.go │ │ │ ├── readers.go │ │ │ ├── temp_unix.go │ │ │ ├── temp_windows.go │ │ │ ├── writeflusher.go │ │ │ └── writers.go │ │ ├── lockfile/ │ │ │ ├── lastwrite.go │ │ │ ├── lockfile.go │ │ │ ├── lockfile_unix.go │ │ │ └── lockfile_windows.go │ │ ├── longpath/ │ │ │ └── longpath.go │ │ ├── mount/ │ │ │ ├── flags.go │ │ │ ├── flags_freebsd.go │ │ │ ├── flags_linux.go │ │ │ ├── flags_unsupported.go │ │ │ ├── mount.go │ │ │ ├── mounter_freebsd.go │ │ │ ├── mounter_linux.go │ │ │ ├── mounter_unsupported.go │ │ │ ├── mountinfo.go │ │ │ ├── mountinfo_linux.go │ │ │ ├── sharedsubtree_linux.go │ │ │ ├── unmount_unix.go │ │ │ └── unmount_unsupported.go │ │ ├── reexec/ │ │ │ ├── README.md │ │ │ ├── command_freebsd.go │ │ │ ├── command_linux.go │ │ │ ├── command_unix.go │ │ │ ├── command_unsupported.go │ │ │ ├── command_windows.go │ │ │ └── reexec.go │ │ ├── regexp/ │ │ │ ├── regexp.go │ │ │ ├── regexp_dontprecompile.go │ │ │ └── regexp_precompile.go │ │ ├── system/ │ │ │ ├── chmod.go │ │ │ ├── chtimes.go │ │ │ ├── chtimes_unix.go │ │ │ ├── chtimes_windows.go │ │ │ ├── errors.go │ │ │ ├── exitcode.go │ │ │ ├── init.go │ │ │ ├── init_windows.go │ │ │ ├── lchflags_bsd.go │ │ │ ├── lchown.go │ │ │ ├── lcow_unix.go │ │ │ ├── lcow_windows.go │ │ │ ├── lstat_unix.go │ │ │ ├── lstat_windows.go │ │ │ ├── meminfo.go │ │ │ ├── meminfo_freebsd.go │ │ │ ├── meminfo_linux.go │ │ │ ├── meminfo_solaris.go │ │ │ ├── meminfo_unsupported.go │ │ │ ├── meminfo_windows.go │ │ │ ├── mknod.go │ │ │ ├── mknod_freebsd.go │ │ │ ├── mknod_windows.go │ │ │ ├── path.go │ │ │ ├── path_unix.go │ │ │ ├── path_windows.go │ │ │ ├── process_unix.go │ │ │ ├── rm.go │ │ │ ├── rm_common.go │ │ │ ├── rm_freebsd.go │ │ │ ├── stat_common.go │ │ │ ├── stat_darwin.go │ │ │ ├── stat_freebsd.go │ │ │ ├── stat_linux.go │ │ │ ├── stat_openbsd.go │ │ │ ├── stat_solaris.go │ │ │ ├── stat_unix.go │ │ │ ├── stat_windows.go │ │ │ ├── syscall_unix.go │ │ │ ├── syscall_windows.go │ │ │ ├── umask.go │ │ │ ├── umask_windows.go │ │ │ ├── utimes_freebsd.go │ │ │ ├── utimes_linux.go │ │ │ ├── utimes_unsupported.go │ │ │ ├── xattrs_darwin.go │ │ │ ├── xattrs_linux.go │ │ │ └── xattrs_unsupported.go │ │ └── unshare/ │ │ ├── getenv_linux_cgo.go │ │ ├── getenv_linux_nocgo.go │ │ ├── unshare.c │ │ ├── unshare.go │ │ ├── unshare_cgo.go │ │ ├── unshare_darwin.go │ │ ├── unshare_freebsd.c │ │ ├── unshare_freebsd.go │ │ ├── unshare_gccgo.go │ │ ├── unshare_linux.go │ │ ├── unshare_unsupported.go │ │ └── unshare_unsupported_cgo.go │ ├── coreos/ │ │ ├── go-iptables/ │ │ │ ├── LICENSE │ │ │ ├── NOTICE │ │ │ └── iptables/ │ │ │ ├── iptables.go │ │ │ └── lock.go │ │ ├── go-semver/ │ │ │ ├── LICENSE │ │ │ ├── NOTICE │ │ │ └── semver/ │ │ │ ├── semver.go │ │ │ └── sort.go │ │ └── go-systemd/ │ │ └── v22/ │ │ ├── LICENSE │ │ ├── NOTICE │ │ ├── daemon/ │ │ │ ├── sdnotify.go │ │ │ └── watchdog.go │ │ └── journal/ │ │ ├── journal.go │ │ ├── journal_unix.go │ │ └── journal_windows.go │ ├── creack/ │ │ └── pty/ │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── Dockerfile.golang │ │ ├── LICENSE │ │ ├── README.md │ │ ├── asm_solaris_amd64.s │ │ ├── doc.go │ │ ├── ioctl.go │ │ ├── ioctl_bsd.go │ │ ├── ioctl_inner.go │ │ ├── ioctl_legacy.go │ │ ├── ioctl_solaris.go │ │ ├── ioctl_unsupported.go │ │ ├── mktypes.bash │ │ ├── pty_darwin.go │ │ ├── pty_dragonfly.go │ │ ├── pty_freebsd.go │ │ ├── pty_linux.go │ │ ├── pty_netbsd.go │ │ ├── pty_openbsd.go │ │ ├── pty_solaris.go │ │ ├── pty_unsupported.go │ │ ├── run.go │ │ ├── start.go │ │ ├── start_windows.go │ │ ├── test_crosscompile.sh │ │ ├── winsize.go │ │ ├── winsize_unix.go │ │ ├── winsize_unsupported.go │ │ ├── ztypes_386.go │ │ ├── ztypes_amd64.go │ │ ├── ztypes_arm.go │ │ ├── ztypes_arm64.go │ │ ├── ztypes_dragonfly_amd64.go │ │ ├── ztypes_freebsd_386.go │ │ ├── ztypes_freebsd_amd64.go │ │ ├── ztypes_freebsd_arm.go │ │ ├── ztypes_freebsd_arm64.go │ │ ├── ztypes_freebsd_ppc64.go │ │ ├── ztypes_freebsd_riscv64.go │ │ ├── ztypes_loong64.go │ │ ├── ztypes_mipsx.go │ │ ├── ztypes_netbsd_32bit_int.go │ │ ├── ztypes_openbsd_32bit_int.go │ │ ├── ztypes_ppc.go │ │ ├── ztypes_ppc64.go │ │ ├── ztypes_ppc64le.go │ │ ├── ztypes_riscvx.go │ │ ├── ztypes_s390x.go │ │ └── ztypes_sparcx.go │ ├── davecgh/ │ │ └── go-spew/ │ │ ├── LICENSE │ │ └── spew/ │ │ ├── bypass.go │ │ ├── bypasssafe.go │ │ ├── common.go │ │ ├── config.go │ │ ├── doc.go │ │ ├── dump.go │ │ ├── format.go │ │ └── spew.go │ ├── dblohm7/ │ │ └── wingoes/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── com/ │ │ │ ├── api.go │ │ │ ├── automation/ │ │ │ │ ├── automation.go │ │ │ │ ├── mksyscall.go │ │ │ │ ├── types.go │ │ │ │ └── zsyscall_windows.go │ │ │ ├── com.go │ │ │ ├── globalopts.go │ │ │ ├── guid.go │ │ │ ├── interface.go │ │ │ ├── mksyscall.go │ │ │ ├── object.go │ │ │ ├── process.go │ │ │ ├── stream.go │ │ │ ├── stream_not386.go │ │ │ ├── stream_windows_386.go │ │ │ ├── types.go │ │ │ ├── unknown.go │ │ │ └── zsyscall_windows.go │ │ ├── error.go │ │ ├── guid.go │ │ ├── guid_notwindows.go │ │ ├── guid_windows.go │ │ ├── hresult.go │ │ ├── internal/ │ │ │ └── types.go │ │ ├── osversion.go │ │ ├── pe/ │ │ │ ├── oh.go │ │ │ ├── pe.go │ │ │ ├── pe_386.go │ │ │ ├── pe_amd64.go │ │ │ ├── pe_arm64.go │ │ │ ├── pe_notwindows.go │ │ │ ├── pe_windows.go │ │ │ └── version.go │ │ ├── time.go │ │ └── util.go │ ├── denisbrodbeck/ │ │ └── machineid/ │ │ ├── .gitignore │ │ ├── LICENSE.md │ │ ├── README.md │ │ ├── helper.go │ │ ├── id.go │ │ ├── id_bsd.go │ │ ├── id_darwin.go │ │ ├── id_linux.go │ │ ├── id_windows.go │ │ └── makefile │ ├── digitalocean/ │ │ └── go-smbios/ │ │ ├── AUTHORS │ │ ├── LICENSE.md │ │ └── smbios/ │ │ ├── decoder.go │ │ ├── doc.go │ │ ├── entrypoint.go │ │ ├── fuzz.go │ │ ├── stream_linux.go │ │ ├── stream_memory.go │ │ ├── stream_others.go │ │ ├── stream_unix.go │ │ ├── stream_windows.go │ │ └── structure.go │ ├── dimchansky/ │ │ └── utfbom/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ └── utfbom.go │ ├── distribution/ │ │ └── reference/ │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── CODE-OF-CONDUCT.md │ │ ├── CONTRIBUTING.md │ │ ├── GOVERNANCE.md │ │ ├── LICENSE │ │ ├── MAINTAINERS │ │ ├── Makefile │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── helpers.go │ │ ├── normalize.go │ │ ├── reference.go │ │ ├── regexp.go │ │ └── sort.go │ ├── docker/ │ │ ├── cli/ │ │ │ ├── AUTHORS │ │ │ ├── LICENSE │ │ │ ├── NOTICE │ │ │ └── cli/ │ │ │ └── config/ │ │ │ ├── config.go │ │ │ ├── configfile/ │ │ │ │ ├── file.go │ │ │ │ ├── file_unix.go │ │ │ │ └── file_windows.go │ │ │ ├── credentials/ │ │ │ │ ├── credentials.go │ │ │ │ ├── default_store.go │ │ │ │ ├── default_store_darwin.go │ │ │ │ ├── default_store_linux.go │ │ │ │ ├── default_store_unsupported.go │ │ │ │ ├── default_store_windows.go │ │ │ │ ├── file_store.go │ │ │ │ └── native_store.go │ │ │ └── types/ │ │ │ └── authconfig.go │ │ ├── distribution/ │ │ │ ├── LICENSE │ │ │ └── registry/ │ │ │ └── client/ │ │ │ └── auth/ │ │ │ └── challenge/ │ │ │ ├── addr.go │ │ │ └── authchallenge.go │ │ ├── docker/ │ │ │ ├── AUTHORS │ │ │ ├── LICENSE │ │ │ ├── NOTICE │ │ │ ├── api/ │ │ │ │ ├── README.md │ │ │ │ ├── common.go │ │ │ │ ├── swagger-gen.yaml │ │ │ │ ├── swagger.yaml │ │ │ │ └── types/ │ │ │ │ ├── blkiodev/ │ │ │ │ │ └── blkio.go │ │ │ │ ├── checkpoint/ │ │ │ │ │ ├── list.go │ │ │ │ │ └── options.go │ │ │ │ ├── client.go │ │ │ │ ├── container/ │ │ │ │ │ ├── change_type.go │ │ │ │ │ ├── change_types.go │ │ │ │ │ ├── config.go │ │ │ │ │ ├── container.go │ │ │ │ │ ├── container_top.go │ │ │ │ │ ├── container_update.go │ │ │ │ │ ├── create_request.go │ │ │ │ │ ├── create_response.go │ │ │ │ │ ├── errors.go │ │ │ │ │ ├── exec.go │ │ │ │ │ ├── filesystem_change.go │ │ │ │ │ ├── hostconfig.go │ │ │ │ │ ├── hostconfig_unix.go │ │ │ │ │ ├── hostconfig_windows.go │ │ │ │ │ ├── options.go │ │ │ │ │ ├── stats.go │ │ │ │ │ ├── wait_exit_error.go │ │ │ │ │ ├── wait_response.go │ │ │ │ │ └── waitcondition.go │ │ │ │ ├── error_response.go │ │ │ │ ├── error_response_ext.go │ │ │ │ ├── events/ │ │ │ │ │ └── events.go │ │ │ │ ├── filters/ │ │ │ │ │ ├── errors.go │ │ │ │ │ └── parse.go │ │ │ │ ├── graph_driver_data.go │ │ │ │ ├── id_response.go │ │ │ │ ├── image/ │ │ │ │ │ ├── delete_response.go │ │ │ │ │ ├── image.go │ │ │ │ │ ├── image_history.go │ │ │ │ │ ├── manifest.go │ │ │ │ │ ├── opts.go │ │ │ │ │ └── summary.go │ │ │ │ ├── mount/ │ │ │ │ │ └── mount.go │ │ │ │ ├── network/ │ │ │ │ │ ├── create_response.go │ │ │ │ │ ├── endpoint.go │ │ │ │ │ ├── ipam.go │ │ │ │ │ └── network.go │ │ │ │ ├── plugin.go │ │ │ │ ├── plugin_device.go │ │ │ │ ├── plugin_env.go │ │ │ │ ├── plugin_interface_type.go │ │ │ │ ├── plugin_mount.go │ │ │ │ ├── plugin_responses.go │ │ │ │ ├── port.go │ │ │ │ ├── registry/ │ │ │ │ │ ├── authconfig.go │ │ │ │ │ ├── authenticate.go │ │ │ │ │ ├── registry.go │ │ │ │ │ └── search.go │ │ │ │ ├── strslice/ │ │ │ │ │ └── strslice.go │ │ │ │ ├── swarm/ │ │ │ │ │ ├── common.go │ │ │ │ │ ├── config.go │ │ │ │ │ ├── container.go │ │ │ │ │ ├── network.go │ │ │ │ │ ├── node.go │ │ │ │ │ ├── runtime/ │ │ │ │ │ │ ├── gen.go │ │ │ │ │ │ ├── plugin.pb.go │ │ │ │ │ │ └── plugin.proto │ │ │ │ │ ├── runtime.go │ │ │ │ │ ├── secret.go │ │ │ │ │ ├── service.go │ │ │ │ │ ├── service_create_response.go │ │ │ │ │ ├── service_update_response.go │ │ │ │ │ ├── swarm.go │ │ │ │ │ └── task.go │ │ │ │ ├── system/ │ │ │ │ │ ├── info.go │ │ │ │ │ ├── runtime.go │ │ │ │ │ └── security_opts.go │ │ │ │ ├── time/ │ │ │ │ │ └── timestamp.go │ │ │ │ ├── types.go │ │ │ │ ├── types_deprecated.go │ │ │ │ ├── versions/ │ │ │ │ │ └── compare.go │ │ │ │ └── volume/ │ │ │ │ ├── cluster_volume.go │ │ │ │ ├── create_options.go │ │ │ │ ├── list_response.go │ │ │ │ ├── options.go │ │ │ │ ├── volume.go │ │ │ │ └── volume_update.go │ │ │ ├── client/ │ │ │ │ ├── README.md │ │ │ │ ├── build_cancel.go │ │ │ │ ├── build_prune.go │ │ │ │ ├── checkpoint_create.go │ │ │ │ ├── checkpoint_delete.go │ │ │ │ ├── checkpoint_list.go │ │ │ │ ├── client.go │ │ │ │ ├── client_deprecated.go │ │ │ │ ├── client_unix.go │ │ │ │ ├── client_windows.go │ │ │ │ ├── config_create.go │ │ │ │ ├── config_inspect.go │ │ │ │ ├── config_list.go │ │ │ │ ├── config_remove.go │ │ │ │ ├── config_update.go │ │ │ │ ├── container_attach.go │ │ │ │ ├── container_commit.go │ │ │ │ ├── container_copy.go │ │ │ │ ├── container_create.go │ │ │ │ ├── container_diff.go │ │ │ │ ├── container_exec.go │ │ │ │ ├── container_export.go │ │ │ │ ├── container_inspect.go │ │ │ │ ├── container_kill.go │ │ │ │ ├── container_list.go │ │ │ │ ├── container_logs.go │ │ │ │ ├── container_pause.go │ │ │ │ ├── container_prune.go │ │ │ │ ├── container_remove.go │ │ │ │ ├── container_rename.go │ │ │ │ ├── container_resize.go │ │ │ │ ├── container_restart.go │ │ │ │ ├── container_start.go │ │ │ │ ├── container_stats.go │ │ │ │ ├── container_stop.go │ │ │ │ ├── container_top.go │ │ │ │ ├── container_unpause.go │ │ │ │ ├── container_update.go │ │ │ │ ├── container_wait.go │ │ │ │ ├── disk_usage.go │ │ │ │ ├── distribution_inspect.go │ │ │ │ ├── envvars.go │ │ │ │ ├── errors.go │ │ │ │ ├── events.go │ │ │ │ ├── hijack.go │ │ │ │ ├── image_build.go │ │ │ │ ├── image_create.go │ │ │ │ ├── image_history.go │ │ │ │ ├── image_import.go │ │ │ │ ├── image_inspect.go │ │ │ │ ├── image_list.go │ │ │ │ ├── image_load.go │ │ │ │ ├── image_prune.go │ │ │ │ ├── image_pull.go │ │ │ │ ├── image_push.go │ │ │ │ ├── image_remove.go │ │ │ │ ├── image_save.go │ │ │ │ ├── image_search.go │ │ │ │ ├── image_tag.go │ │ │ │ ├── info.go │ │ │ │ ├── interface.go │ │ │ │ ├── interface_experimental.go │ │ │ │ ├── interface_stable.go │ │ │ │ ├── login.go │ │ │ │ ├── network_connect.go │ │ │ │ ├── network_create.go │ │ │ │ ├── network_disconnect.go │ │ │ │ ├── network_inspect.go │ │ │ │ ├── network_list.go │ │ │ │ ├── network_prune.go │ │ │ │ ├── network_remove.go │ │ │ │ ├── node_inspect.go │ │ │ │ ├── node_list.go │ │ │ │ ├── node_remove.go │ │ │ │ ├── node_update.go │ │ │ │ ├── options.go │ │ │ │ ├── ping.go │ │ │ │ ├── plugin_create.go │ │ │ │ ├── plugin_disable.go │ │ │ │ ├── plugin_enable.go │ │ │ │ ├── plugin_inspect.go │ │ │ │ ├── plugin_install.go │ │ │ │ ├── plugin_list.go │ │ │ │ ├── plugin_push.go │ │ │ │ ├── plugin_remove.go │ │ │ │ ├── plugin_set.go │ │ │ │ ├── plugin_upgrade.go │ │ │ │ ├── request.go │ │ │ │ ├── secret_create.go │ │ │ │ ├── secret_inspect.go │ │ │ │ ├── secret_list.go │ │ │ │ ├── secret_remove.go │ │ │ │ ├── secret_update.go │ │ │ │ ├── service_create.go │ │ │ │ ├── service_inspect.go │ │ │ │ ├── service_list.go │ │ │ │ ├── service_logs.go │ │ │ │ ├── service_remove.go │ │ │ │ ├── service_update.go │ │ │ │ ├── swarm_get_unlock_key.go │ │ │ │ ├── swarm_init.go │ │ │ │ ├── swarm_inspect.go │ │ │ │ ├── swarm_join.go │ │ │ │ ├── swarm_leave.go │ │ │ │ ├── swarm_unlock.go │ │ │ │ ├── swarm_update.go │ │ │ │ ├── task_inspect.go │ │ │ │ ├── task_list.go │ │ │ │ ├── task_logs.go │ │ │ │ ├── utils.go │ │ │ │ ├── version.go │ │ │ │ ├── volume_create.go │ │ │ │ ├── volume_inspect.go │ │ │ │ ├── volume_list.go │ │ │ │ ├── volume_prune.go │ │ │ │ ├── volume_remove.go │ │ │ │ └── volume_update.go │ │ │ ├── errdefs/ │ │ │ │ ├── defs.go │ │ │ │ ├── doc.go │ │ │ │ ├── helpers.go │ │ │ │ ├── http_helpers.go │ │ │ │ └── is.go │ │ │ ├── internal/ │ │ │ │ └── multierror/ │ │ │ │ └── multierror.go │ │ │ └── pkg/ │ │ │ ├── homedir/ │ │ │ │ ├── homedir.go │ │ │ │ ├── homedir_linux.go │ │ │ │ └── homedir_others.go │ │ │ └── longpath/ │ │ │ └── longpath.go │ │ ├── docker-credential-helpers/ │ │ │ ├── LICENSE │ │ │ ├── client/ │ │ │ │ ├── client.go │ │ │ │ └── command.go │ │ │ └── credentials/ │ │ │ ├── credentials.go │ │ │ ├── error.go │ │ │ ├── helper.go │ │ │ └── version.go │ │ ├── go-connections/ │ │ │ ├── LICENSE │ │ │ ├── nat/ │ │ │ │ ├── nat.go │ │ │ │ ├── parse.go │ │ │ │ └── sort.go │ │ │ ├── sockets/ │ │ │ │ ├── README.md │ │ │ │ ├── inmem_socket.go │ │ │ │ ├── proxy.go │ │ │ │ ├── sockets.go │ │ │ │ ├── sockets_unix.go │ │ │ │ ├── sockets_windows.go │ │ │ │ ├── tcp_socket.go │ │ │ │ └── unix_socket.go │ │ │ └── tlsconfig/ │ │ │ ├── certpool.go │ │ │ ├── config.go │ │ │ └── config_client_ciphers.go │ │ └── go-units/ │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── MAINTAINERS │ │ ├── README.md │ │ ├── circle.yml │ │ ├── duration.go │ │ ├── size.go │ │ └── ulimit.go │ ├── dustin/ │ │ └── go-humanize/ │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.markdown │ │ ├── big.go │ │ ├── bigbytes.go │ │ ├── bytes.go │ │ ├── comma.go │ │ ├── commaf.go │ │ ├── ftoa.go │ │ ├── humanize.go │ │ ├── number.go │ │ ├── ordinals.go │ │ ├── si.go │ │ └── times.go │ ├── emicklei/ │ │ └── go-restful/ │ │ └── v3/ │ │ ├── .gitignore │ │ ├── .goconvey │ │ ├── .travis.yml │ │ ├── CHANGES.md │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── Srcfile │ │ ├── bench_test.sh │ │ ├── compress.go │ │ ├── compressor_cache.go │ │ ├── compressor_pools.go │ │ ├── compressors.go │ │ ├── constants.go │ │ ├── container.go │ │ ├── cors_filter.go │ │ ├── coverage.sh │ │ ├── curly.go │ │ ├── curly_route.go │ │ ├── custom_verb.go │ │ ├── doc.go │ │ ├── entity_accessors.go │ │ ├── extensions.go │ │ ├── filter.go │ │ ├── filter_adapter.go │ │ ├── jsr311.go │ │ ├── log/ │ │ │ └── log.go │ │ ├── logger.go │ │ ├── mime.go │ │ ├── options_filter.go │ │ ├── parameter.go │ │ ├── path_expression.go │ │ ├── path_processor.go │ │ ├── request.go │ │ ├── response.go │ │ ├── route.go │ │ ├── route_builder.go │ │ ├── route_reader.go │ │ ├── router.go │ │ ├── service_error.go │ │ ├── web_service.go │ │ └── web_service_container.go │ ├── erikgeiser/ │ │ └── coninput/ │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── keycodes.go │ │ ├── mode.go │ │ ├── read.go │ │ └── records.go │ ├── evanphx/ │ │ └── json-patch/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── errors.go │ │ ├── merge.go │ │ ├── patch.go │ │ └── v5/ │ │ ├── LICENSE │ │ ├── errors.go │ │ ├── internal/ │ │ │ └── json/ │ │ │ ├── decode.go │ │ │ ├── encode.go │ │ │ ├── fold.go │ │ │ ├── fuzz.go │ │ │ ├── indent.go │ │ │ ├── scanner.go │ │ │ ├── stream.go │ │ │ ├── tables.go │ │ │ └── tags.go │ │ ├── merge.go │ │ └── patch.go │ ├── felixge/ │ │ └── httpsnoop/ │ │ ├── .gitignore │ │ ├── LICENSE.txt │ │ ├── Makefile │ │ ├── README.md │ │ ├── capture_metrics.go │ │ ├── docs.go │ │ ├── wrap_generated_gteq_1.8.go │ │ └── wrap_generated_lt_1.8.go │ ├── fsnotify/ │ │ └── fsnotify/ │ │ ├── .cirrus.yml │ │ ├── .editorconfig │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .mailmap │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── backend_fen.go │ │ ├── backend_inotify.go │ │ ├── backend_kqueue.go │ │ ├── backend_other.go │ │ ├── backend_windows.go │ │ ├── fsnotify.go │ │ ├── mkdoc.zsh │ │ ├── system_bsd.go │ │ └── system_darwin.go │ ├── fxamacker/ │ │ └── cbor/ │ │ └── v2/ │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── CODE_OF_CONDUCT.md │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── bytestring.go │ │ ├── cache.go │ │ ├── common.go │ │ ├── decode.go │ │ ├── diagnose.go │ │ ├── doc.go │ │ ├── encode.go │ │ ├── encode_map.go │ │ ├── encode_map_go117.go │ │ ├── simplevalue.go │ │ ├── stream.go │ │ ├── structfields.go │ │ ├── tag.go │ │ └── valid.go │ ├── gaissmai/ │ │ └── bart/ │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── base_index.go │ │ ├── dumper.go │ │ ├── jsonify.go │ │ ├── node.go │ │ ├── stringify.go │ │ └── table.go │ ├── ghodss/ │ │ └── yaml/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── fields.go │ │ └── yaml.go │ ├── go-json-experiment/ │ │ └── json/ │ │ ├── AUTHORS │ │ ├── CONTRIBUTORS │ │ ├── LICENSE │ │ ├── README.md │ │ ├── arshal.go │ │ ├── arshal_any.go │ │ ├── arshal_default.go │ │ ├── arshal_funcs.go │ │ ├── arshal_inlined.go │ │ ├── arshal_methods.go │ │ ├── arshal_time.go │ │ ├── doc.go │ │ ├── errors.go │ │ ├── fields.go │ │ ├── fold.go │ │ ├── intern.go │ │ ├── internal/ │ │ │ ├── internal.go │ │ │ ├── jsonflags/ │ │ │ │ └── flags.go │ │ │ ├── jsonopts/ │ │ │ │ └── options.go │ │ │ └── jsonwire/ │ │ │ ├── decode.go │ │ │ ├── encode.go │ │ │ └── wire.go │ │ ├── jsontext/ │ │ │ ├── decode.go │ │ │ ├── doc.go │ │ │ ├── encode.go │ │ │ ├── errors.go │ │ │ ├── export.go │ │ │ ├── options.go │ │ │ ├── pools.go │ │ │ ├── quote.go │ │ │ ├── state.go │ │ │ ├── token.go │ │ │ └── value.go │ │ └── options.go │ ├── go-logr/ │ │ ├── logr/ │ │ │ ├── .golangci.yaml │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── SECURITY.md │ │ │ ├── context.go │ │ │ ├── context_noslog.go │ │ │ ├── context_slog.go │ │ │ ├── discard.go │ │ │ ├── funcr/ │ │ │ │ ├── funcr.go │ │ │ │ └── slogsink.go │ │ │ ├── logr.go │ │ │ ├── sloghandler.go │ │ │ ├── slogr.go │ │ │ └── slogsink.go │ │ └── stdr/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── stdr.go │ ├── go-ole/ │ │ └── go-ole/ │ │ ├── .travis.yml │ │ ├── ChangeLog.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── appveyor.yml │ │ ├── com.go │ │ ├── com_func.go │ │ ├── connect.go │ │ ├── constants.go │ │ ├── error.go │ │ ├── error_func.go │ │ ├── error_windows.go │ │ ├── guid.go │ │ ├── iconnectionpoint.go │ │ ├── iconnectionpoint_func.go │ │ ├── iconnectionpoint_windows.go │ │ ├── iconnectionpointcontainer.go │ │ ├── iconnectionpointcontainer_func.go │ │ ├── iconnectionpointcontainer_windows.go │ │ ├── idispatch.go │ │ ├── idispatch_func.go │ │ ├── idispatch_windows.go │ │ ├── ienumvariant.go │ │ ├── ienumvariant_func.go │ │ ├── ienumvariant_windows.go │ │ ├── iinspectable.go │ │ ├── iinspectable_func.go │ │ ├── iinspectable_windows.go │ │ ├── iprovideclassinfo.go │ │ ├── iprovideclassinfo_func.go │ │ ├── iprovideclassinfo_windows.go │ │ ├── itypeinfo.go │ │ ├── itypeinfo_func.go │ │ ├── itypeinfo_windows.go │ │ ├── iunknown.go │ │ ├── iunknown_func.go │ │ ├── iunknown_windows.go │ │ ├── ole.go │ │ ├── oleutil/ │ │ │ ├── connection.go │ │ │ ├── connection_func.go │ │ │ ├── connection_windows.go │ │ │ ├── go-get.go │ │ │ └── oleutil.go │ │ ├── safearray.go │ │ ├── safearray_func.go │ │ ├── safearray_windows.go │ │ ├── safearrayconversion.go │ │ ├── safearrayslices.go │ │ ├── utility.go │ │ ├── variables.go │ │ ├── variant.go │ │ ├── variant_386.go │ │ ├── variant_amd64.go │ │ ├── variant_arm.go │ │ ├── variant_arm64.go │ │ ├── variant_date_386.go │ │ ├── variant_date_amd64.go │ │ ├── variant_date_arm.go │ │ ├── variant_date_arm64.go │ │ ├── variant_ppc64le.go │ │ ├── variant_s390x.go │ │ ├── vt_string.go │ │ ├── winrt.go │ │ └── winrt_doc.go │ ├── go-openapi/ │ │ ├── jsonpointer/ │ │ │ ├── .editorconfig │ │ │ ├── .gitignore │ │ │ ├── .golangci.yml │ │ │ ├── CODE_OF_CONDUCT.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── pointer.go │ │ └── jsonreference/ │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── CODE_OF_CONDUCT.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── internal/ │ │ │ └── normalize_url.go │ │ └── reference.go │ ├── go-viper/ │ │ └── mapstructure/ │ │ └── v2/ │ │ ├── .editorconfig │ │ ├── .envrc │ │ ├── .gitignore │ │ ├── .golangci.yaml │ │ ├── CHANGELOG.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── decode_hooks.go │ │ ├── flake.nix │ │ ├── internal/ │ │ │ └── errors/ │ │ │ ├── errors.go │ │ │ ├── join.go │ │ │ └── join_go1_19.go │ │ ├── mapstructure.go │ │ ├── reflect_go1_19.go │ │ └── reflect_go1_20.go │ ├── godbus/ │ │ └── dbus/ │ │ └── v5/ │ │ ├── .cirrus.yml │ │ ├── .golangci.yml │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── MAINTAINERS │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── auth.go │ │ ├── auth_anonymous.go │ │ ├── auth_external.go │ │ ├── auth_sha1.go │ │ ├── call.go │ │ ├── conn.go │ │ ├── conn_darwin.go │ │ ├── conn_other.go │ │ ├── conn_unix.go │ │ ├── conn_windows.go │ │ ├── dbus.go │ │ ├── decoder.go │ │ ├── default_handler.go │ │ ├── doc.go │ │ ├── encoder.go │ │ ├── escape.go │ │ ├── export.go │ │ ├── homedir.go │ │ ├── match.go │ │ ├── message.go │ │ ├── object.go │ │ ├── sequence.go │ │ ├── sequential_handler.go │ │ ├── server_interfaces.go │ │ ├── sig.go │ │ ├── transport_darwin.go │ │ ├── transport_generic.go │ │ ├── transport_nonce_tcp.go │ │ ├── transport_tcp.go │ │ ├── transport_unix.go │ │ ├── transport_unixcred_dragonfly.go │ │ ├── transport_unixcred_freebsd.go │ │ ├── transport_unixcred_linux.go │ │ ├── transport_unixcred_netbsd.go │ │ ├── transport_unixcred_openbsd.go │ │ ├── transport_zos.go │ │ ├── variant.go │ │ ├── variant_lexer.go │ │ └── variant_parser.go │ ├── gofrs/ │ │ └── flock/ │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── build.sh │ │ ├── flock.go │ │ ├── flock_others.go │ │ ├── flock_unix.go │ │ ├── flock_unix_fcntl.go │ │ └── flock_windows.go │ ├── gogo/ │ │ └── protobuf/ │ │ ├── AUTHORS │ │ ├── CONTRIBUTORS │ │ ├── LICENSE │ │ ├── gogoproto/ │ │ │ ├── Makefile │ │ │ ├── doc.go │ │ │ ├── gogo.pb.go │ │ │ ├── gogo.pb.golden │ │ │ ├── gogo.proto │ │ │ └── helper.go │ │ ├── proto/ │ │ │ ├── Makefile │ │ │ ├── clone.go │ │ │ ├── custom_gogo.go │ │ │ ├── decode.go │ │ │ ├── deprecated.go │ │ │ ├── discard.go │ │ │ ├── duration.go │ │ │ ├── duration_gogo.go │ │ │ ├── encode.go │ │ │ ├── encode_gogo.go │ │ │ ├── equal.go │ │ │ ├── extensions.go │ │ │ ├── extensions_gogo.go │ │ │ ├── lib.go │ │ │ ├── lib_gogo.go │ │ │ ├── message_set.go │ │ │ ├── pointer_reflect.go │ │ │ ├── pointer_reflect_gogo.go │ │ │ ├── pointer_unsafe.go │ │ │ ├── pointer_unsafe_gogo.go │ │ │ ├── properties.go │ │ │ ├── properties_gogo.go │ │ │ ├── skip_gogo.go │ │ │ ├── table_marshal.go │ │ │ ├── table_marshal_gogo.go │ │ │ ├── table_merge.go │ │ │ ├── table_unmarshal.go │ │ │ ├── table_unmarshal_gogo.go │ │ │ ├── text.go │ │ │ ├── text_gogo.go │ │ │ ├── text_parser.go │ │ │ ├── timestamp.go │ │ │ ├── timestamp_gogo.go │ │ │ ├── wrappers.go │ │ │ └── wrappers_gogo.go │ │ ├── protoc-gen-gogo/ │ │ │ └── descriptor/ │ │ │ ├── Makefile │ │ │ ├── descriptor.go │ │ │ ├── descriptor.pb.go │ │ │ ├── descriptor_gostring.gen.go │ │ │ └── helper.go │ │ └── sortkeys/ │ │ └── sortkeys.go │ ├── golang/ │ │ ├── groupcache/ │ │ │ ├── LICENSE │ │ │ └── lru/ │ │ │ └── lru.go │ │ └── protobuf/ │ │ ├── AUTHORS │ │ ├── CONTRIBUTORS │ │ ├── LICENSE │ │ ├── proto/ │ │ │ ├── buffer.go │ │ │ ├── defaults.go │ │ │ ├── deprecated.go │ │ │ ├── discard.go │ │ │ ├── extensions.go │ │ │ ├── properties.go │ │ │ ├── proto.go │ │ │ ├── registry.go │ │ │ ├── text_decode.go │ │ │ ├── text_encode.go │ │ │ ├── wire.go │ │ │ └── wrappers.go │ │ └── ptypes/ │ │ ├── any/ │ │ │ └── any.pb.go │ │ └── timestamp/ │ │ └── timestamp.pb.go │ ├── golang-jwt/ │ │ └── jwt/ │ │ └── v4/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── MIGRATION_GUIDE.md │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── VERSION_HISTORY.md │ │ ├── claims.go │ │ ├── doc.go │ │ ├── ecdsa.go │ │ ├── ecdsa_utils.go │ │ ├── ed25519.go │ │ ├── ed25519_utils.go │ │ ├── errors.go │ │ ├── hmac.go │ │ ├── map_claims.go │ │ ├── none.go │ │ ├── parser.go │ │ ├── parser_option.go │ │ ├── rsa.go │ │ ├── rsa_pss.go │ │ ├── rsa_utils.go │ │ ├── signing_method.go │ │ ├── staticcheck.conf │ │ ├── token.go │ │ └── types.go │ ├── google/ │ │ ├── btree/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── btree.go │ │ │ └── btree_generic.go │ │ ├── cel-go/ │ │ │ ├── LICENSE │ │ │ ├── cel/ │ │ │ │ ├── BUILD.bazel │ │ │ │ ├── cel.go │ │ │ │ ├── decls.go │ │ │ │ ├── env.go │ │ │ │ ├── folding.go │ │ │ │ ├── inlining.go │ │ │ │ ├── io.go │ │ │ │ ├── library.go │ │ │ │ ├── macro.go │ │ │ │ ├── optimizer.go │ │ │ │ ├── options.go │ │ │ │ ├── program.go │ │ │ │ └── validator.go │ │ │ ├── checker/ │ │ │ │ ├── BUILD.bazel │ │ │ │ ├── checker.go │ │ │ │ ├── cost.go │ │ │ │ ├── decls/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ └── decls.go │ │ │ │ ├── env.go │ │ │ │ ├── errors.go │ │ │ │ ├── format.go │ │ │ │ ├── mapping.go │ │ │ │ ├── options.go │ │ │ │ ├── printer.go │ │ │ │ ├── scopes.go │ │ │ │ └── types.go │ │ │ ├── common/ │ │ │ │ ├── BUILD.bazel │ │ │ │ ├── ast/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ ├── ast.go │ │ │ │ │ ├── conversion.go │ │ │ │ │ ├── expr.go │ │ │ │ │ ├── factory.go │ │ │ │ │ └── navigable.go │ │ │ │ ├── containers/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ └── container.go │ │ │ │ ├── cost.go │ │ │ │ ├── debug/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ └── debug.go │ │ │ │ ├── decls/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ └── decls.go │ │ │ │ ├── doc.go │ │ │ │ ├── error.go │ │ │ │ ├── errors.go │ │ │ │ ├── functions/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ └── functions.go │ │ │ │ ├── location.go │ │ │ │ ├── operators/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ └── operators.go │ │ │ │ ├── overloads/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ └── overloads.go │ │ │ │ ├── runes/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ └── buffer.go │ │ │ │ ├── source.go │ │ │ │ ├── stdlib/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ └── standard.go │ │ │ │ └── types/ │ │ │ │ ├── BUILD.bazel │ │ │ │ ├── any_value.go │ │ │ │ ├── bool.go │ │ │ │ ├── bytes.go │ │ │ │ ├── compare.go │ │ │ │ ├── doc.go │ │ │ │ ├── double.go │ │ │ │ ├── duration.go │ │ │ │ ├── err.go │ │ │ │ ├── int.go │ │ │ │ ├── iterator.go │ │ │ │ ├── json_value.go │ │ │ │ ├── list.go │ │ │ │ ├── map.go │ │ │ │ ├── null.go │ │ │ │ ├── object.go │ │ │ │ ├── optional.go │ │ │ │ ├── overflow.go │ │ │ │ ├── pb/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ ├── checked.go │ │ │ │ │ ├── enum.go │ │ │ │ │ ├── equal.go │ │ │ │ │ ├── file.go │ │ │ │ │ ├── pb.go │ │ │ │ │ └── type.go │ │ │ │ ├── provider.go │ │ │ │ ├── ref/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ ├── provider.go │ │ │ │ │ └── reference.go │ │ │ │ ├── string.go │ │ │ │ ├── timestamp.go │ │ │ │ ├── traits/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ ├── comparer.go │ │ │ │ │ ├── container.go │ │ │ │ │ ├── field_tester.go │ │ │ │ │ ├── indexer.go │ │ │ │ │ ├── iterator.go │ │ │ │ │ ├── lister.go │ │ │ │ │ ├── mapper.go │ │ │ │ │ ├── matcher.go │ │ │ │ │ ├── math.go │ │ │ │ │ ├── receiver.go │ │ │ │ │ ├── sizer.go │ │ │ │ │ ├── traits.go │ │ │ │ │ └── zeroer.go │ │ │ │ ├── types.go │ │ │ │ ├── uint.go │ │ │ │ ├── unknown.go │ │ │ │ └── util.go │ │ │ ├── ext/ │ │ │ │ ├── BUILD.bazel │ │ │ │ ├── README.md │ │ │ │ ├── bindings.go │ │ │ │ ├── comprehensions.go │ │ │ │ ├── encoders.go │ │ │ │ ├── formatting.go │ │ │ │ ├── guards.go │ │ │ │ ├── lists.go │ │ │ │ ├── math.go │ │ │ │ ├── native.go │ │ │ │ ├── protos.go │ │ │ │ ├── sets.go │ │ │ │ └── strings.go │ │ │ ├── interpreter/ │ │ │ │ ├── BUILD.bazel │ │ │ │ ├── activation.go │ │ │ │ ├── attribute_patterns.go │ │ │ │ ├── attributes.go │ │ │ │ ├── decorators.go │ │ │ │ ├── dispatcher.go │ │ │ │ ├── evalstate.go │ │ │ │ ├── functions/ │ │ │ │ │ ├── BUILD.bazel │ │ │ │ │ └── functions.go │ │ │ │ ├── interpretable.go │ │ │ │ ├── interpreter.go │ │ │ │ ├── optimizations.go │ │ │ │ ├── planner.go │ │ │ │ ├── prune.go │ │ │ │ └── runtimecost.go │ │ │ └── parser/ │ │ │ ├── BUILD.bazel │ │ │ ├── errors.go │ │ │ ├── gen/ │ │ │ │ ├── BUILD.bazel │ │ │ │ ├── CEL.g4 │ │ │ │ ├── CEL.interp │ │ │ │ ├── CEL.tokens │ │ │ │ ├── CELLexer.interp │ │ │ │ ├── CELLexer.tokens │ │ │ │ ├── cel_base_listener.go │ │ │ │ ├── cel_base_visitor.go │ │ │ │ ├── cel_lexer.go │ │ │ │ ├── cel_listener.go │ │ │ │ ├── cel_parser.go │ │ │ │ ├── cel_visitor.go │ │ │ │ ├── doc.go │ │ │ │ └── generate.sh │ │ │ ├── helper.go │ │ │ ├── input.go │ │ │ ├── macro.go │ │ │ ├── options.go │ │ │ ├── parser.go │ │ │ ├── unescape.go │ │ │ └── unparser.go │ │ ├── gnostic-models/ │ │ │ ├── LICENSE │ │ │ ├── compiler/ │ │ │ │ ├── README.md │ │ │ │ ├── context.go │ │ │ │ ├── error.go │ │ │ │ ├── extensions.go │ │ │ │ ├── helpers.go │ │ │ │ ├── main.go │ │ │ │ └── reader.go │ │ │ ├── extensions/ │ │ │ │ ├── README.md │ │ │ │ ├── extension.pb.go │ │ │ │ ├── extension.proto │ │ │ │ └── extensions.go │ │ │ ├── jsonschema/ │ │ │ │ ├── README.md │ │ │ │ ├── base.go │ │ │ │ ├── display.go │ │ │ │ ├── models.go │ │ │ │ ├── operations.go │ │ │ │ ├── reader.go │ │ │ │ ├── schema.json │ │ │ │ └── writer.go │ │ │ ├── openapiv2/ │ │ │ │ ├── OpenAPIv2.go │ │ │ │ ├── OpenAPIv2.pb.go │ │ │ │ ├── OpenAPIv2.proto │ │ │ │ ├── README.md │ │ │ │ ├── document.go │ │ │ │ └── openapi-2.0.json │ │ │ └── openapiv3/ │ │ │ ├── OpenAPIv3.go │ │ │ ├── OpenAPIv3.pb.go │ │ │ ├── OpenAPIv3.proto │ │ │ ├── README.md │ │ │ ├── annotations.pb.go │ │ │ ├── annotations.proto │ │ │ └── document.go │ │ ├── go-cmp/ │ │ │ ├── LICENSE │ │ │ └── cmp/ │ │ │ ├── compare.go │ │ │ ├── export.go │ │ │ ├── internal/ │ │ │ │ ├── diff/ │ │ │ │ │ ├── debug_disable.go │ │ │ │ │ ├── debug_enable.go │ │ │ │ │ └── diff.go │ │ │ │ ├── flags/ │ │ │ │ │ └── flags.go │ │ │ │ ├── function/ │ │ │ │ │ └── func.go │ │ │ │ └── value/ │ │ │ │ ├── name.go │ │ │ │ ├── pointer.go │ │ │ │ └── sort.go │ │ │ ├── options.go │ │ │ ├── path.go │ │ │ ├── report.go │ │ │ ├── report_compare.go │ │ │ ├── report_references.go │ │ │ ├── report_reflect.go │ │ │ ├── report_slices.go │ │ │ ├── report_text.go │ │ │ └── report_value.go │ │ ├── go-containerregistry/ │ │ │ ├── LICENSE │ │ │ ├── internal/ │ │ │ │ ├── and/ │ │ │ │ │ └── and_closer.go │ │ │ │ ├── compression/ │ │ │ │ │ └── compression.go │ │ │ │ ├── estargz/ │ │ │ │ │ └── estargz.go │ │ │ │ ├── gzip/ │ │ │ │ │ └── zip.go │ │ │ │ ├── redact/ │ │ │ │ │ └── redact.go │ │ │ │ ├── retry/ │ │ │ │ │ ├── retry.go │ │ │ │ │ └── wait/ │ │ │ │ │ └── kubernetes_apimachinery_wait.go │ │ │ │ ├── verify/ │ │ │ │ │ └── verify.go │ │ │ │ └── zstd/ │ │ │ │ └── zstd.go │ │ │ └── pkg/ │ │ │ ├── authn/ │ │ │ │ ├── README.md │ │ │ │ ├── anon.go │ │ │ │ ├── auth.go │ │ │ │ ├── authn.go │ │ │ │ ├── basic.go │ │ │ │ ├── bearer.go │ │ │ │ ├── doc.go │ │ │ │ ├── keychain.go │ │ │ │ ├── kubernetes/ │ │ │ │ │ ├── LICENSE │ │ │ │ │ └── keychain.go │ │ │ │ └── multikeychain.go │ │ │ ├── compression/ │ │ │ │ └── compression.go │ │ │ ├── logs/ │ │ │ │ └── logs.go │ │ │ ├── name/ │ │ │ │ ├── README.md │ │ │ │ ├── check.go │ │ │ │ ├── digest.go │ │ │ │ ├── doc.go │ │ │ │ ├── errors.go │ │ │ │ ├── options.go │ │ │ │ ├── ref.go │ │ │ │ ├── registry.go │ │ │ │ ├── repository.go │ │ │ │ └── tag.go │ │ │ └── v1/ │ │ │ ├── config.go │ │ │ ├── doc.go │ │ │ ├── empty/ │ │ │ │ ├── README.md │ │ │ │ ├── doc.go │ │ │ │ ├── image.go │ │ │ │ └── index.go │ │ │ ├── google/ │ │ │ │ ├── README.md │ │ │ │ ├── auth.go │ │ │ │ ├── doc.go │ │ │ │ ├── keychain.go │ │ │ │ ├── list.go │ │ │ │ └── options.go │ │ │ ├── hash.go │ │ │ ├── image.go │ │ │ ├── index.go │ │ │ ├── layer.go │ │ │ ├── manifest.go │ │ │ ├── match/ │ │ │ │ └── match.go │ │ │ ├── mutate/ │ │ │ │ ├── README.md │ │ │ │ ├── doc.go │ │ │ │ ├── image.go │ │ │ │ ├── index.go │ │ │ │ ├── mutate.go │ │ │ │ └── rebase.go │ │ │ ├── partial/ │ │ │ │ ├── README.md │ │ │ │ ├── compressed.go │ │ │ │ ├── doc.go │ │ │ │ ├── image.go │ │ │ │ ├── index.go │ │ │ │ ├── uncompressed.go │ │ │ │ └── with.go │ │ │ ├── platform.go │ │ │ ├── progress.go │ │ │ ├── remote/ │ │ │ │ ├── README.md │ │ │ │ ├── catalog.go │ │ │ │ ├── check.go │ │ │ │ ├── delete.go │ │ │ │ ├── descriptor.go │ │ │ │ ├── doc.go │ │ │ │ ├── fetcher.go │ │ │ │ ├── image.go │ │ │ │ ├── index.go │ │ │ │ ├── layer.go │ │ │ │ ├── list.go │ │ │ │ ├── mount.go │ │ │ │ ├── multi_write.go │ │ │ │ ├── options.go │ │ │ │ ├── progress.go │ │ │ │ ├── puller.go │ │ │ │ ├── pusher.go │ │ │ │ ├── referrers.go │ │ │ │ ├── schema1.go │ │ │ │ ├── transport/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── basic.go │ │ │ │ │ ├── bearer.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── error.go │ │ │ │ │ ├── logger.go │ │ │ │ │ ├── ping.go │ │ │ │ │ ├── retry.go │ │ │ │ │ ├── schemer.go │ │ │ │ │ ├── scope.go │ │ │ │ │ ├── transport.go │ │ │ │ │ └── useragent.go │ │ │ │ └── write.go │ │ │ ├── stream/ │ │ │ │ ├── README.md │ │ │ │ └── layer.go │ │ │ ├── tarball/ │ │ │ │ ├── README.md │ │ │ │ ├── doc.go │ │ │ │ ├── image.go │ │ │ │ ├── layer.go │ │ │ │ └── write.go │ │ │ ├── types/ │ │ │ │ └── types.go │ │ │ └── zz_deepcopy_generated.go │ │ ├── go-github/ │ │ │ └── v30/ │ │ │ ├── AUTHORS │ │ │ ├── LICENSE │ │ │ └── github/ │ │ │ ├── actions.go │ │ │ ├── actions_artifacts.go │ │ │ ├── actions_runners.go │ │ │ ├── actions_secrets.go │ │ │ ├── actions_workflow_jobs.go │ │ │ ├── actions_workflow_runs.go │ │ │ ├── actions_workflows.go │ │ │ ├── activity.go │ │ │ ├── activity_events.go │ │ │ ├── activity_notifications.go │ │ │ ├── activity_star.go │ │ │ ├── activity_watching.go │ │ │ ├── admin.go │ │ │ ├── admin_orgs.go │ │ │ ├── admin_stats.go │ │ │ ├── admin_users.go │ │ │ ├── apps.go │ │ │ ├── apps_installation.go │ │ │ ├── apps_manifest.go │ │ │ ├── apps_marketplace.go │ │ │ ├── authorizations.go │ │ │ ├── checks.go │ │ │ ├── doc.go │ │ │ ├── event.go │ │ │ ├── event_types.go │ │ │ ├── gists.go │ │ │ ├── gists_comments.go │ │ │ ├── git.go │ │ │ ├── git_blobs.go │ │ │ ├── git_commits.go │ │ │ ├── git_refs.go │ │ │ ├── git_tags.go │ │ │ ├── git_trees.go │ │ │ ├── github-accessors.go │ │ │ ├── github.go │ │ │ ├── gitignore.go │ │ │ ├── interactions.go │ │ │ ├── interactions_orgs.go │ │ │ ├── interactions_repos.go │ │ │ ├── issues.go │ │ │ ├── issues_assignees.go │ │ │ ├── issues_comments.go │ │ │ ├── issues_events.go │ │ │ ├── issues_labels.go │ │ │ ├── issues_milestones.go │ │ │ ├── issues_timeline.go │ │ │ ├── licenses.go │ │ │ ├── messages.go │ │ │ ├── migrations.go │ │ │ ├── migrations_source_import.go │ │ │ ├── migrations_user.go │ │ │ ├── misc.go │ │ │ ├── orgs.go │ │ │ ├── orgs_hooks.go │ │ │ ├── orgs_members.go │ │ │ ├── orgs_outside_collaborators.go │ │ │ ├── orgs_projects.go │ │ │ ├── orgs_users_blocking.go │ │ │ ├── projects.go │ │ │ ├── pulls.go │ │ │ ├── pulls_comments.go │ │ │ ├── pulls_reviewers.go │ │ │ ├── pulls_reviews.go │ │ │ ├── reactions.go │ │ │ ├── repos.go │ │ │ ├── repos_collaborators.go │ │ │ ├── repos_comments.go │ │ │ ├── repos_commits.go │ │ │ ├── repos_community_health.go │ │ │ ├── repos_contents.go │ │ │ ├── repos_deployments.go │ │ │ ├── repos_forks.go │ │ │ ├── repos_hooks.go │ │ │ ├── repos_invitations.go │ │ │ ├── repos_keys.go │ │ │ ├── repos_merging.go │ │ │ ├── repos_pages.go │ │ │ ├── repos_prereceive_hooks.go │ │ │ ├── repos_projects.go │ │ │ ├── repos_releases.go │ │ │ ├── repos_stats.go │ │ │ ├── repos_statuses.go │ │ │ ├── repos_traffic.go │ │ │ ├── search.go │ │ │ ├── strings.go │ │ │ ├── teams.go │ │ │ ├── teams_discussion_comments.go │ │ │ ├── teams_discussions.go │ │ │ ├── teams_members.go │ │ │ ├── timestamp.go │ │ │ ├── users.go │ │ │ ├── users_administration.go │ │ │ ├── users_blocking.go │ │ │ ├── users_emails.go │ │ │ ├── users_followers.go │ │ │ ├── users_gpg_keys.go │ │ │ ├── users_keys.go │ │ │ ├── users_projects.go │ │ │ ├── with_appengine.go │ │ │ └── without_appengine.go │ │ ├── go-querystring/ │ │ │ ├── LICENSE │ │ │ └── query/ │ │ │ └── encode.go │ │ ├── gofuzz/ │ │ │ ├── .travis.yml │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── bytesource/ │ │ │ │ └── bytesource.go │ │ │ ├── doc.go │ │ │ └── fuzz.go │ │ ├── nftables/ │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── alignedbuff/ │ │ │ │ └── alignedbuff.go │ │ │ ├── binaryutil/ │ │ │ │ └── binaryutil.go │ │ │ ├── chain.go │ │ │ ├── compat_policy.go │ │ │ ├── conn.go │ │ │ ├── counter.go │ │ │ ├── doc.go │ │ │ ├── expr/ │ │ │ │ ├── bitwise.go │ │ │ │ ├── byteorder.go │ │ │ │ ├── connlimit.go │ │ │ │ ├── counter.go │ │ │ │ ├── ct.go │ │ │ │ ├── dup.go │ │ │ │ ├── dynset.go │ │ │ │ ├── expr.go │ │ │ │ ├── exthdr.go │ │ │ │ ├── fib.go │ │ │ │ ├── flow_offload.go │ │ │ │ ├── hash.go │ │ │ │ ├── immediate.go │ │ │ │ ├── limit.go │ │ │ │ ├── log.go │ │ │ │ ├── lookup.go │ │ │ │ ├── match.go │ │ │ │ ├── nat.go │ │ │ │ ├── notrack.go │ │ │ │ ├── numgen.go │ │ │ │ ├── objref.go │ │ │ │ ├── payload.go │ │ │ │ ├── queue.go │ │ │ │ ├── quota.go │ │ │ │ ├── range.go │ │ │ │ ├── redirect.go │ │ │ │ ├── reject.go │ │ │ │ ├── rt.go │ │ │ │ ├── socket.go │ │ │ │ ├── target.go │ │ │ │ ├── tproxy.go │ │ │ │ └── verdict.go │ │ │ ├── flowtable.go │ │ │ ├── internal/ │ │ │ │ └── parseexprfunc/ │ │ │ │ └── parseexprfunc.go │ │ │ ├── monitor.go │ │ │ ├── obj.go │ │ │ ├── quota.go │ │ │ ├── rule.go │ │ │ ├── set.go │ │ │ ├── table.go │ │ │ ├── util.go │ │ │ └── xt/ │ │ │ ├── info.go │ │ │ ├── match_addrtype.go │ │ │ ├── match_conntrack.go │ │ │ ├── match_tcp.go │ │ │ ├── match_udp.go │ │ │ ├── target_dnat.go │ │ │ ├── target_masquerade_ip.go │ │ │ ├── unknown.go │ │ │ ├── util.go │ │ │ └── xt.go │ │ ├── pprof/ │ │ │ ├── AUTHORS │ │ │ ├── CONTRIBUTORS │ │ │ ├── LICENSE │ │ │ └── profile/ │ │ │ ├── encode.go │ │ │ ├── filter.go │ │ │ ├── index.go │ │ │ ├── legacy_java_profile.go │ │ │ ├── legacy_profile.go │ │ │ ├── merge.go │ │ │ ├── profile.go │ │ │ ├── proto.go │ │ │ └── prune.go │ │ ├── shlex/ │ │ │ ├── COPYING │ │ │ ├── README │ │ │ └── shlex.go │ │ └── uuid/ │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── CONTRIBUTORS │ │ ├── LICENSE │ │ ├── README.md │ │ ├── dce.go │ │ ├── doc.go │ │ ├── hash.go │ │ ├── marshal.go │ │ ├── node.go │ │ ├── node_js.go │ │ ├── node_net.go │ │ ├── null.go │ │ ├── sql.go │ │ ├── time.go │ │ ├── util.go │ │ ├── uuid.go │ │ ├── version1.go │ │ ├── version4.go │ │ ├── version6.go │ │ └── version7.go │ ├── gorilla/ │ │ ├── csrf/ │ │ │ ├── .editorconfig │ │ │ ├── .gitignore │ │ │ ├── Gopkg.toml │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── context.go │ │ │ ├── csrf.go │ │ │ ├── doc.go │ │ │ ├── helpers.go │ │ │ ├── options.go │ │ │ ├── store.go │ │ │ └── store_legacy.go │ │ ├── handlers/ │ │ │ ├── .editorconfig │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── canonical.go │ │ │ ├── compress.go │ │ │ ├── cors.go │ │ │ ├── doc.go │ │ │ ├── handlers.go │ │ │ ├── logging.go │ │ │ ├── proxy_headers.go │ │ │ └── recovery.go │ │ ├── securecookie/ │ │ │ ├── .editorconfig │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── doc.go │ │ │ └── securecookie.go │ │ └── websocket/ │ │ ├── .gitignore │ │ ├── AUTHORS │ │ ├── LICENSE │ │ ├── README.md │ │ ├── client.go │ │ ├── compression.go │ │ ├── conn.go │ │ ├── doc.go │ │ ├── join.go │ │ ├── json.go │ │ ├── mask.go │ │ ├── mask_safe.go │ │ ├── prepared.go │ │ ├── proxy.go │ │ ├── server.go │ │ ├── tls_handshake.go │ │ ├── tls_handshake_116.go │ │ ├── util.go │ │ └── x_net_proxy.go │ ├── grpc-ecosystem/ │ │ └── grpc-gateway/ │ │ └── v2/ │ │ ├── LICENSE │ │ ├── internal/ │ │ │ └── httprule/ │ │ │ ├── BUILD.bazel │ │ │ ├── compile.go │ │ │ ├── fuzz.go │ │ │ ├── parse.go │ │ │ └── types.go │ │ ├── runtime/ │ │ │ ├── BUILD.bazel │ │ │ ├── context.go │ │ │ ├── convert.go │ │ │ ├── doc.go │ │ │ ├── errors.go │ │ │ ├── fieldmask.go │ │ │ ├── handler.go │ │ │ ├── marshal_httpbodyproto.go │ │ │ ├── marshal_json.go │ │ │ ├── marshal_jsonpb.go │ │ │ ├── marshal_proto.go │ │ │ ├── marshaler.go │ │ │ ├── marshaler_registry.go │ │ │ ├── mux.go │ │ │ ├── pattern.go │ │ │ ├── proto2_convert.go │ │ │ └── query.go │ │ └── utilities/ │ │ ├── BUILD.bazel │ │ ├── doc.go │ │ ├── pattern.go │ │ ├── readerfactory.go │ │ ├── string_array_flag.go │ │ └── trie.go │ ├── hashicorp/ │ │ ├── errwrap/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── errwrap.go │ │ ├── go-cleanhttp/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── cleanhttp.go │ │ │ ├── doc.go │ │ │ └── handlers.go │ │ └── go-multierror/ │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── append.go │ │ ├── flatten.go │ │ ├── format.go │ │ ├── group.go │ │ ├── multierror.go │ │ ├── prefix.go │ │ └── sort.go │ ├── hdevalence/ │ │ └── ed25519consensus/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── batch.go │ │ └── ed25519.go │ ├── illarion/ │ │ └── gonotify/ │ │ └── v2/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── dirwatcher.go │ │ ├── event.go │ │ ├── filewatcher.go │ │ └── inotify.go │ ├── in-toto/ │ │ └── in-toto-golang/ │ │ ├── LICENSE │ │ └── in_toto/ │ │ ├── attestations.go │ │ ├── certconstraint.go │ │ ├── envelope.go │ │ ├── hashlib.go │ │ ├── keylib.go │ │ ├── match.go │ │ ├── model.go │ │ ├── rulelib.go │ │ ├── runlib.go │ │ ├── slsa_provenance/ │ │ │ ├── common/ │ │ │ │ └── common.go │ │ │ ├── v0.1/ │ │ │ │ └── provenance.go │ │ │ ├── v0.2/ │ │ │ │ └── provenance.go │ │ │ └── v1/ │ │ │ └── provenance.go │ │ ├── util.go │ │ ├── util_unix.go │ │ ├── util_windows.go │ │ └── verifylib.go │ ├── inconshreveable/ │ │ ├── go-update/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── apply.go │ │ │ ├── doc.go │ │ │ ├── hide_noop.go │ │ │ ├── hide_windows.go │ │ │ ├── internal/ │ │ │ │ ├── binarydist/ │ │ │ │ │ ├── License │ │ │ │ │ ├── Readme.md │ │ │ │ │ ├── bzip2.go │ │ │ │ │ ├── diff.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── encoding.go │ │ │ │ │ ├── patch.go │ │ │ │ │ └── seek.go │ │ │ │ └── osext/ │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── osext.go │ │ │ │ ├── osext_plan9.go │ │ │ │ ├── osext_procfs.go │ │ │ │ ├── osext_sysctl.go │ │ │ │ └── osext_windows.go │ │ │ ├── patcher.go │ │ │ └── verifier.go │ │ └── mousetrap/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── trap_others.go │ │ └── trap_windows.go │ ├── insomniacslk/ │ │ └── dhcp/ │ │ ├── CONTRIBUTORS.md │ │ ├── LICENSE │ │ ├── dhcpv4/ │ │ │ ├── bindtointerface.go │ │ │ ├── defaults.go │ │ │ ├── dhcpv4.go │ │ │ ├── modifiers.go │ │ │ ├── option_generic.go │ │ │ ├── option_ip.go │ │ │ ├── option_ip_address_lease_time.go │ │ │ ├── option_ips.go │ │ │ ├── option_maximum_dhcp_message_size.go │ │ │ ├── option_message_type.go │ │ │ ├── option_misc.go │ │ │ ├── option_parameter_request_list.go │ │ │ ├── option_relay_agent_information.go │ │ │ ├── option_routes.go │ │ │ ├── option_string.go │ │ │ ├── option_strings.go │ │ │ ├── option_subnet_mask.go │ │ │ ├── option_vivc.go │ │ │ ├── options.go │ │ │ └── types.go │ │ ├── iana/ │ │ │ ├── archtype.go │ │ │ ├── entid.go │ │ │ ├── hwtypes.go │ │ │ ├── iana.go │ │ │ └── statuscodes.go │ │ ├── interfaces/ │ │ │ ├── bindtodevice_bsd.go │ │ │ ├── bindtodevice_darwin.go │ │ │ ├── bindtodevice_linux.go │ │ │ ├── bindtodevice_windows.go │ │ │ └── interfaces.go │ │ └── rfc1035label/ │ │ └── label.go │ ├── jmespath/ │ │ └── go-jmespath/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── api.go │ │ ├── astnodetype_string.go │ │ ├── functions.go │ │ ├── interpreter.go │ │ ├── lexer.go │ │ ├── parser.go │ │ ├── toktype_string.go │ │ └── util.go │ ├── joho/ │ │ └── godotenv/ │ │ ├── .gitignore │ │ ├── LICENCE │ │ ├── README.md │ │ ├── godotenv.go │ │ └── parser.go │ ├── josharian/ │ │ ├── intern/ │ │ │ ├── README.md │ │ │ ├── intern.go │ │ │ └── license.md │ │ └── native/ │ │ ├── doc.go │ │ ├── endian_generic.go │ │ ├── license │ │ └── readme.md │ ├── jsimonetti/ │ │ └── rtnetlink/ │ │ ├── .gitignore │ │ ├── LICENSE.md │ │ ├── Makefile.fuzz │ │ ├── README.md │ │ ├── address.go │ │ ├── conn.go │ │ ├── doc.go │ │ ├── endian.go │ │ ├── fuzz-shell.nix │ │ ├── internal/ │ │ │ └── unix/ │ │ │ ├── types_linux.go │ │ │ └── types_other.go │ │ ├── link.go │ │ ├── neigh.go │ │ ├── route.go │ │ └── rule.go │ ├── julienschmidt/ │ │ └── httprouter/ │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── path.go │ │ ├── router.go │ │ └── tree.go │ ├── k0kubun/ │ │ └── go-ansi/ │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── cursor.go │ │ ├── cursor_windows.go │ │ ├── display.go │ │ ├── display_windows.go │ │ ├── output.go │ │ ├── output_windows.go │ │ ├── print.go │ │ └── syscall_windows.go │ ├── kballard/ │ │ └── go-shellquote/ │ │ ├── LICENSE │ │ ├── README │ │ ├── doc.go │ │ ├── quote.go │ │ └── unquote.go │ ├── klauspost/ │ │ └── compress/ │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .goreleaser.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── compressible.go │ │ ├── fse/ │ │ │ ├── README.md │ │ │ ├── bitreader.go │ │ │ ├── bitwriter.go │ │ │ ├── bytereader.go │ │ │ ├── compress.go │ │ │ ├── decompress.go │ │ │ └── fse.go │ │ ├── gen.sh │ │ ├── huff0/ │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── bitreader.go │ │ │ ├── bitwriter.go │ │ │ ├── compress.go │ │ │ ├── decompress.go │ │ │ ├── decompress_amd64.go │ │ │ ├── decompress_amd64.s │ │ │ ├── decompress_generic.go │ │ │ └── huff0.go │ │ ├── internal/ │ │ │ ├── cpuinfo/ │ │ │ │ ├── cpuinfo.go │ │ │ │ ├── cpuinfo_amd64.go │ │ │ │ └── cpuinfo_amd64.s │ │ │ └── snapref/ │ │ │ ├── LICENSE │ │ │ ├── decode.go │ │ │ ├── decode_other.go │ │ │ ├── encode.go │ │ │ ├── encode_other.go │ │ │ └── snappy.go │ │ ├── s2sx.mod │ │ ├── s2sx.sum │ │ └── zstd/ │ │ ├── README.md │ │ ├── bitreader.go │ │ ├── bitwriter.go │ │ ├── blockdec.go │ │ ├── blockenc.go │ │ ├── blocktype_string.go │ │ ├── bytebuf.go │ │ ├── bytereader.go │ │ ├── decodeheader.go │ │ ├── decoder.go │ │ ├── decoder_options.go │ │ ├── dict.go │ │ ├── enc_base.go │ │ ├── enc_best.go │ │ ├── enc_better.go │ │ ├── enc_dfast.go │ │ ├── enc_fast.go │ │ ├── encoder.go │ │ ├── encoder_options.go │ │ ├── framedec.go │ │ ├── frameenc.go │ │ ├── fse_decoder.go │ │ ├── fse_decoder_amd64.go │ │ ├── fse_decoder_amd64.s │ │ ├── fse_decoder_generic.go │ │ ├── fse_encoder.go │ │ ├── fse_predefined.go │ │ ├── hash.go │ │ ├── history.go │ │ ├── internal/ │ │ │ └── xxhash/ │ │ │ ├── LICENSE.txt │ │ │ ├── README.md │ │ │ ├── xxhash.go │ │ │ ├── xxhash_amd64.s │ │ │ ├── xxhash_arm64.s │ │ │ ├── xxhash_asm.go │ │ │ ├── xxhash_other.go │ │ │ └── xxhash_safe.go │ │ ├── matchlen_amd64.go │ │ ├── matchlen_amd64.s │ │ ├── matchlen_generic.go │ │ ├── seqdec.go │ │ ├── seqdec_amd64.go │ │ ├── seqdec_amd64.s │ │ ├── seqdec_generic.go │ │ ├── seqenc.go │ │ ├── snappy.go │ │ ├── zip.go │ │ └── zstd.go │ ├── kortschak/ │ │ └── wol/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── wol.go │ ├── kr/ │ │ └── fs/ │ │ ├── LICENSE │ │ ├── Readme │ │ ├── filesystem.go │ │ └── walk.go │ ├── kylelemons/ │ │ └── godebug/ │ │ ├── LICENSE │ │ └── diff/ │ │ └── diff.go │ ├── liggitt/ │ │ └── tabwriter/ │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ └── tabwriter.go │ ├── loft-sh/ │ │ ├── admin-apis/ │ │ │ ├── LICENSE │ │ │ └── pkg/ │ │ │ └── licenseapi/ │ │ │ ├── features.go │ │ │ ├── features.yaml │ │ │ ├── features_internal.go │ │ │ ├── generate.go │ │ │ ├── license.go │ │ │ ├── license_analytics.go │ │ │ ├── license_announcement.go │ │ │ ├── license_block_request.go │ │ │ ├── license_button.go │ │ │ ├── license_domain_token.go │ │ │ ├── license_feature.go │ │ │ ├── license_invoice.go │ │ │ ├── license_limit.go │ │ │ ├── license_module.go │ │ │ ├── license_new.go │ │ │ ├── license_plan.go │ │ │ ├── license_resource_count.go │ │ │ ├── license_routes.go │ │ │ ├── license_trial.go │ │ │ ├── names.go │ │ │ ├── offline_license.go │ │ │ ├── request_auth.go │ │ │ ├── request_chat_auth.go │ │ │ ├── request_generic.go │ │ │ ├── request_instance.go │ │ │ └── zz_generated.deepcopy.go │ │ ├── agentapi/ │ │ │ └── v4/ │ │ │ ├── LICENSE │ │ │ └── pkg/ │ │ │ └── apis/ │ │ │ └── loft/ │ │ │ ├── cluster/ │ │ │ │ ├── doc.go │ │ │ │ ├── inject.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── chartinfo_types.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── feature_types.go │ │ │ │ │ ├── helmrelease_types.go │ │ │ │ │ ├── sleepmodeconfig_types.go │ │ │ │ │ ├── zz_generated.api.register.go │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ ├── zz_generated.api.register.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.defaults.go │ │ │ └── storage/ │ │ │ └── v1/ │ │ │ ├── clusterquota_types.go │ │ │ ├── condition_types.go │ │ │ ├── doc.go │ │ │ ├── groupversion_info.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.defaults.go │ │ ├── analytics-client/ │ │ │ ├── LICENSE │ │ │ └── client/ │ │ │ ├── buffer.go │ │ │ ├── client.go │ │ │ ├── noop.go │ │ │ └── types.go │ │ ├── api/ │ │ │ └── v4/ │ │ │ ├── LICENSE │ │ │ └── pkg/ │ │ │ ├── apis/ │ │ │ │ ├── audit/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── event_types.go │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ ├── management/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── inject.go │ │ │ │ │ ├── install/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── install.go │ │ │ │ │ │ └── zz_generated.api.register.go │ │ │ │ │ ├── types.go │ │ │ │ │ ├── v1/ │ │ │ │ │ │ ├── agentauditevent_types.go │ │ │ │ │ │ ├── announcement_types.go │ │ │ │ │ │ ├── app_credentials_types.go │ │ │ │ │ │ ├── app_types.go │ │ │ │ │ │ ├── backup_apply_types.go │ │ │ │ │ │ ├── backup_types.go │ │ │ │ │ │ ├── cluster_accesskey_types.go │ │ │ │ │ │ ├── cluster_agentconfig_types.go │ │ │ │ │ │ ├── cluster_charts_types.go │ │ │ │ │ │ ├── cluster_domain_types.go │ │ │ │ │ │ ├── cluster_memberaccess_types.go │ │ │ │ │ │ ├── cluster_members_types.go │ │ │ │ │ │ ├── cluster_reset_types.go │ │ │ │ │ │ ├── cluster_types.go │ │ │ │ │ │ ├── cluster_virtualclusterdefaults_types.go │ │ │ │ │ │ ├── clusteraccess_types.go │ │ │ │ │ │ ├── clusterroletemplate_types.go │ │ │ │ │ │ ├── config_types.go │ │ │ │ │ │ ├── devpodenvironmenttemplate_types.go │ │ │ │ │ │ ├── devpodworkspaceinstance_cancel_types.go │ │ │ │ │ │ ├── devpodworkspaceinstance_download_types.go │ │ │ │ │ │ ├── devpodworkspaceinstance_log_types.go │ │ │ │ │ │ ├── devpodworkspaceinstance_stop_types.go │ │ │ │ │ │ ├── devpodworkspaceinstance_tasks_types.go │ │ │ │ │ │ ├── devpodworkspaceinstance_troubleshoot_types.go │ │ │ │ │ │ ├── devpodworkspaceinstance_types.go │ │ │ │ │ │ ├── devpodworkspaceinstance_up_types.go │ │ │ │ │ │ ├── devpodworkspacepreset_types.go │ │ │ │ │ │ ├── devpodworkspacetemplate_types.go │ │ │ │ │ │ ├── directclusterendpointtokens_types.go │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── event_types.go │ │ │ │ │ │ ├── feature_types.go │ │ │ │ │ │ ├── ingressauthtoken_types.go │ │ │ │ │ │ ├── kiosk_types.go │ │ │ │ │ │ ├── license_token_types.go │ │ │ │ │ │ ├── license_types.go │ │ │ │ │ │ ├── licenserequest_types.go │ │ │ │ │ │ ├── loftupgrade_types.go │ │ │ │ │ │ ├── oidc_client_types.go │ │ │ │ │ │ ├── options.go │ │ │ │ │ │ ├── ownedaccesskey_types.go │ │ │ │ │ │ ├── project_chartinfo_types.go │ │ │ │ │ │ ├── project_charts_types.go │ │ │ │ │ │ ├── project_clusters_types.go │ │ │ │ │ │ ├── project_importspace_types.go │ │ │ │ │ │ ├── project_members_types.go │ │ │ │ │ │ ├── project_migratespaceinstance_types.go │ │ │ │ │ │ ├── project_migratevirtualclusterinstance_types.go │ │ │ │ │ │ ├── project_secret_types.go │ │ │ │ │ │ ├── project_templates_types.go │ │ │ │ │ │ ├── project_types.go │ │ │ │ │ │ ├── redirecttoken_types.go │ │ │ │ │ │ ├── resetaccesskey_types.go │ │ │ │ │ │ ├── self_types.go │ │ │ │ │ │ ├── selfsubjectaccessreview_types.go │ │ │ │ │ │ ├── sharedsecret_types.go │ │ │ │ │ │ ├── spaceinstance_types.go │ │ │ │ │ │ ├── spacetemplate_types.go │ │ │ │ │ │ ├── subjectaccessreview_types.go │ │ │ │ │ │ ├── task_log_types.go │ │ │ │ │ │ ├── task_types.go │ │ │ │ │ │ ├── team_accesskeys_types.go │ │ │ │ │ │ ├── team_clusters_types.go │ │ │ │ │ │ ├── team_object_permissions_types.go │ │ │ │ │ │ ├── team_permissions_types.go │ │ │ │ │ │ ├── team_types.go │ │ │ │ │ │ ├── translateresourcename_types.go │ │ │ │ │ │ ├── user_accesskeys_types.go │ │ │ │ │ │ ├── user_clusters_types.go │ │ │ │ │ │ ├── user_object_permissions_types.go │ │ │ │ │ │ ├── user_permissions_types.go │ │ │ │ │ │ ├── user_profile_types.go │ │ │ │ │ │ ├── user_types.go │ │ │ │ │ │ ├── virtualcluster_convertconfig_types.go │ │ │ │ │ │ ├── virtualcluster_register_types.go │ │ │ │ │ │ ├── virtualcluster_schema.go │ │ │ │ │ │ ├── virtualclusterinstance_accesskey_types.go │ │ │ │ │ │ ├── virtualclusterinstance_externaldatabase_types.go │ │ │ │ │ │ ├── virtualclusterinstance_kubeconfig_types.go │ │ │ │ │ │ ├── virtualclusterinstance_log_types.go │ │ │ │ │ │ ├── virtualclusterinstance_types.go │ │ │ │ │ │ ├── virtualclustertemplate_types.go │ │ │ │ │ │ ├── zz_generated.api.register.go │ │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ │ ├── zz_generated.api.register.go │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ ├── storage/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── accesskey_types.go │ │ │ │ │ ├── app_types.go │ │ │ │ │ ├── cluster_types.go │ │ │ │ │ ├── clusteraccess_types.go │ │ │ │ │ ├── clusterroletemplate_types.go │ │ │ │ │ ├── devpodenvironmenttemplate_types.go │ │ │ │ │ ├── devpodworkspaceinstance_types.go │ │ │ │ │ ├── devpodworkspacepreset_types.go │ │ │ │ │ ├── devpodworkspacetemplate_types.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── groupversion_info.go │ │ │ │ │ ├── networkpeer_types.go │ │ │ │ │ ├── project_types.go │ │ │ │ │ ├── sharedsecret_types.go │ │ │ │ │ ├── spaceinstance_types.go │ │ │ │ │ ├── spacetemplate_types.go │ │ │ │ │ ├── task_types.go │ │ │ │ │ ├── team_types.go │ │ │ │ │ ├── user_types.go │ │ │ │ │ ├── virtualclusterinstance_types.go │ │ │ │ │ ├── virtualclustertemplate_types.go │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ ├── ui/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── ui_types.go │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ └── virtualcluster/ │ │ │ │ ├── doc.go │ │ │ │ ├── inject.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── helmrelease_types.go │ │ │ │ │ ├── zz_generated.api.register.go │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ ├── zz_generated.api.register.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.defaults.go │ │ │ ├── auth/ │ │ │ │ └── types.go │ │ │ ├── clientset/ │ │ │ │ └── versioned/ │ │ │ │ ├── clientset.go │ │ │ │ ├── scheme/ │ │ │ │ │ ├── doc.go │ │ │ │ │ └── register.go │ │ │ │ └── typed/ │ │ │ │ ├── management/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── agentauditevent.go │ │ │ │ │ ├── announcement.go │ │ │ │ │ ├── app.go │ │ │ │ │ ├── backup.go │ │ │ │ │ ├── cluster.go │ │ │ │ │ ├── clusteraccess.go │ │ │ │ │ ├── clusterroletemplate.go │ │ │ │ │ ├── config.go │ │ │ │ │ ├── convertvirtualclusterconfig.go │ │ │ │ │ ├── devpodenvironmenttemplate.go │ │ │ │ │ ├── devpodworkspaceinstance.go │ │ │ │ │ ├── devpodworkspacepreset.go │ │ │ │ │ ├── devpodworkspacetemplate.go │ │ │ │ │ ├── directclusterendpointtoken.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── event.go │ │ │ │ │ ├── feature.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── ingressauthtoken.go │ │ │ │ │ ├── license.go │ │ │ │ │ ├── licensetoken.go │ │ │ │ │ ├── loftupgrade.go │ │ │ │ │ ├── management_client.go │ │ │ │ │ ├── oidcclient.go │ │ │ │ │ ├── ownedaccesskey.go │ │ │ │ │ ├── project.go │ │ │ │ │ ├── projectsecret.go │ │ │ │ │ ├── redirecttoken.go │ │ │ │ │ ├── registervirtualcluster.go │ │ │ │ │ ├── resetaccesskey.go │ │ │ │ │ ├── self.go │ │ │ │ │ ├── selfsubjectaccessreview.go │ │ │ │ │ ├── sharedsecret.go │ │ │ │ │ ├── spaceinstance.go │ │ │ │ │ ├── spacetemplate.go │ │ │ │ │ ├── subjectaccessreview.go │ │ │ │ │ ├── task.go │ │ │ │ │ ├── team.go │ │ │ │ │ ├── translatevclusterresourcename.go │ │ │ │ │ ├── user.go │ │ │ │ │ ├── virtualclusterinstance.go │ │ │ │ │ ├── virtualclusterschema.go │ │ │ │ │ └── virtualclustertemplate.go │ │ │ │ ├── storage/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── accesskey.go │ │ │ │ │ ├── app.go │ │ │ │ │ ├── cluster.go │ │ │ │ │ ├── clusteraccess.go │ │ │ │ │ ├── clusterroletemplate.go │ │ │ │ │ ├── devpodenvironmenttemplate.go │ │ │ │ │ ├── devpodworkspaceinstance.go │ │ │ │ │ ├── devpodworkspacepreset.go │ │ │ │ │ ├── devpodworkspacetemplate.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── networkpeer.go │ │ │ │ │ ├── project.go │ │ │ │ │ ├── sharedsecret.go │ │ │ │ │ ├── spaceinstance.go │ │ │ │ │ ├── spacetemplate.go │ │ │ │ │ ├── storage_client.go │ │ │ │ │ ├── task.go │ │ │ │ │ ├── team.go │ │ │ │ │ ├── user.go │ │ │ │ │ ├── virtualclusterinstance.go │ │ │ │ │ └── virtualclustertemplate.go │ │ │ │ └── virtualcluster/ │ │ │ │ └── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── helmrelease.go │ │ │ │ └── virtualcluster_client.go │ │ │ ├── devpod/ │ │ │ │ ├── cloneoptions.go │ │ │ │ ├── platformoptions.go │ │ │ │ ├── runner.pb.go │ │ │ │ ├── runner.proto │ │ │ │ └── runner_grpc.pb.go │ │ │ ├── informers/ │ │ │ │ └── externalversions/ │ │ │ │ ├── factory.go │ │ │ │ ├── generic.go │ │ │ │ ├── internalinterfaces/ │ │ │ │ │ └── factory_interfaces.go │ │ │ │ ├── management/ │ │ │ │ │ ├── interface.go │ │ │ │ │ └── v1/ │ │ │ │ │ ├── agentauditevent.go │ │ │ │ │ ├── announcement.go │ │ │ │ │ ├── app.go │ │ │ │ │ ├── backup.go │ │ │ │ │ ├── cluster.go │ │ │ │ │ ├── clusteraccess.go │ │ │ │ │ ├── clusterroletemplate.go │ │ │ │ │ ├── config.go │ │ │ │ │ ├── convertvirtualclusterconfig.go │ │ │ │ │ ├── devpodenvironmenttemplate.go │ │ │ │ │ ├── devpodworkspaceinstance.go │ │ │ │ │ ├── devpodworkspacepreset.go │ │ │ │ │ ├── devpodworkspacetemplate.go │ │ │ │ │ ├── directclusterendpointtoken.go │ │ │ │ │ ├── event.go │ │ │ │ │ ├── feature.go │ │ │ │ │ ├── ingressauthtoken.go │ │ │ │ │ ├── interface.go │ │ │ │ │ ├── license.go │ │ │ │ │ ├── licensetoken.go │ │ │ │ │ ├── loftupgrade.go │ │ │ │ │ ├── oidcclient.go │ │ │ │ │ ├── ownedaccesskey.go │ │ │ │ │ ├── project.go │ │ │ │ │ ├── projectsecret.go │ │ │ │ │ ├── redirecttoken.go │ │ │ │ │ ├── registervirtualcluster.go │ │ │ │ │ ├── resetaccesskey.go │ │ │ │ │ ├── self.go │ │ │ │ │ ├── selfsubjectaccessreview.go │ │ │ │ │ ├── sharedsecret.go │ │ │ │ │ ├── spaceinstance.go │ │ │ │ │ ├── spacetemplate.go │ │ │ │ │ ├── subjectaccessreview.go │ │ │ │ │ ├── task.go │ │ │ │ │ ├── team.go │ │ │ │ │ ├── translatevclusterresourcename.go │ │ │ │ │ ├── user.go │ │ │ │ │ ├── virtualclusterinstance.go │ │ │ │ │ ├── virtualclusterschema.go │ │ │ │ │ └── virtualclustertemplate.go │ │ │ │ ├── storage/ │ │ │ │ │ ├── interface.go │ │ │ │ │ └── v1/ │ │ │ │ │ ├── accesskey.go │ │ │ │ │ ├── app.go │ │ │ │ │ ├── cluster.go │ │ │ │ │ ├── clusteraccess.go │ │ │ │ │ ├── clusterroletemplate.go │ │ │ │ │ ├── devpodenvironmenttemplate.go │ │ │ │ │ ├── devpodworkspaceinstance.go │ │ │ │ │ ├── devpodworkspacepreset.go │ │ │ │ │ ├── devpodworkspacetemplate.go │ │ │ │ │ ├── interface.go │ │ │ │ │ ├── networkpeer.go │ │ │ │ │ ├── project.go │ │ │ │ │ ├── sharedsecret.go │ │ │ │ │ ├── spaceinstance.go │ │ │ │ │ ├── spacetemplate.go │ │ │ │ │ ├── task.go │ │ │ │ │ ├── team.go │ │ │ │ │ ├── user.go │ │ │ │ │ ├── virtualclusterinstance.go │ │ │ │ │ └── virtualclustertemplate.go │ │ │ │ └── virtualcluster/ │ │ │ │ ├── interface.go │ │ │ │ └── v1/ │ │ │ │ ├── helmrelease.go │ │ │ │ └── interface.go │ │ │ ├── listers/ │ │ │ │ ├── management/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── agentauditevent.go │ │ │ │ │ ├── announcement.go │ │ │ │ │ ├── app.go │ │ │ │ │ ├── backup.go │ │ │ │ │ ├── cluster.go │ │ │ │ │ ├── clusteraccess.go │ │ │ │ │ ├── clusterroletemplate.go │ │ │ │ │ ├── config.go │ │ │ │ │ ├── convertvirtualclusterconfig.go │ │ │ │ │ ├── devpodenvironmenttemplate.go │ │ │ │ │ ├── devpodworkspaceinstance.go │ │ │ │ │ ├── devpodworkspacepreset.go │ │ │ │ │ ├── devpodworkspacetemplate.go │ │ │ │ │ ├── directclusterendpointtoken.go │ │ │ │ │ ├── event.go │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── feature.go │ │ │ │ │ ├── ingressauthtoken.go │ │ │ │ │ ├── license.go │ │ │ │ │ ├── licensetoken.go │ │ │ │ │ ├── loftupgrade.go │ │ │ │ │ ├── oidcclient.go │ │ │ │ │ ├── ownedaccesskey.go │ │ │ │ │ ├── project.go │ │ │ │ │ ├── projectsecret.go │ │ │ │ │ ├── redirecttoken.go │ │ │ │ │ ├── registervirtualcluster.go │ │ │ │ │ ├── resetaccesskey.go │ │ │ │ │ ├── self.go │ │ │ │ │ ├── selfsubjectaccessreview.go │ │ │ │ │ ├── sharedsecret.go │ │ │ │ │ ├── spaceinstance.go │ │ │ │ │ ├── spacetemplate.go │ │ │ │ │ ├── subjectaccessreview.go │ │ │ │ │ ├── task.go │ │ │ │ │ ├── team.go │ │ │ │ │ ├── translatevclusterresourcename.go │ │ │ │ │ ├── user.go │ │ │ │ │ ├── virtualclusterinstance.go │ │ │ │ │ ├── virtualclusterschema.go │ │ │ │ │ └── virtualclustertemplate.go │ │ │ │ ├── storage/ │ │ │ │ │ └── v1/ │ │ │ │ │ ├── accesskey.go │ │ │ │ │ ├── app.go │ │ │ │ │ ├── cluster.go │ │ │ │ │ ├── clusteraccess.go │ │ │ │ │ ├── clusterroletemplate.go │ │ │ │ │ ├── devpodenvironmenttemplate.go │ │ │ │ │ ├── devpodworkspaceinstance.go │ │ │ │ │ ├── devpodworkspacepreset.go │ │ │ │ │ ├── devpodworkspacetemplate.go │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── networkpeer.go │ │ │ │ │ ├── project.go │ │ │ │ │ ├── sharedsecret.go │ │ │ │ │ ├── spaceinstance.go │ │ │ │ │ ├── spacetemplate.go │ │ │ │ │ ├── task.go │ │ │ │ │ ├── team.go │ │ │ │ │ ├── user.go │ │ │ │ │ ├── virtualclusterinstance.go │ │ │ │ │ └── virtualclustertemplate.go │ │ │ │ └── virtualcluster/ │ │ │ │ └── v1/ │ │ │ │ ├── expansion_generated.go │ │ │ │ └── helmrelease.go │ │ │ └── managerfactory/ │ │ │ └── types.go │ │ ├── apiserver/ │ │ │ └── pkg/ │ │ │ └── builders/ │ │ │ ├── api_group_builder.go │ │ │ ├── api_unversioned_resource_builder.go │ │ │ ├── api_version_builder.go │ │ │ ├── api_versioned_resource_builder.go │ │ │ ├── default_storage_strategy.go │ │ │ ├── resource_interfaces.go │ │ │ ├── scheme.go │ │ │ └── shortcut_storage_wrapper.go │ │ ├── log/ │ │ │ ├── .devcontainer.json │ │ │ ├── .golangci.yaml │ │ │ ├── LICENSE │ │ │ ├── discard_logger.go │ │ │ ├── file_logger.go │ │ │ ├── hash/ │ │ │ │ └── hash.go │ │ │ ├── logger.go │ │ │ ├── scanner/ │ │ │ │ └── scanner.go │ │ │ ├── stream_logger.go │ │ │ ├── survey/ │ │ │ │ └── survey.go │ │ │ ├── table/ │ │ │ │ └── table.go │ │ │ └── terminal/ │ │ │ └── tty.go │ │ ├── programming-language-detection/ │ │ │ └── pkg/ │ │ │ └── detector/ │ │ │ └── detector.go │ │ └── ssh/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── agent.go │ │ ├── circle.yml │ │ ├── conn.go │ │ ├── context.go │ │ ├── doc.go │ │ ├── options.go │ │ ├── server.go │ │ ├── session.go │ │ ├── ssh.go │ │ ├── streamlocal.go │ │ ├── tcpip.go │ │ ├── util.go │ │ └── wrap.go │ ├── mailru/ │ │ └── easyjson/ │ │ ├── LICENSE │ │ ├── buffer/ │ │ │ └── pool.go │ │ ├── jlexer/ │ │ │ ├── bytestostr.go │ │ │ ├── bytestostr_nounsafe.go │ │ │ ├── error.go │ │ │ └── lexer.go │ │ └── jwriter/ │ │ └── writer.go │ ├── mattn/ │ │ ├── go-colorable/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── colorable_appengine.go │ │ │ ├── colorable_others.go │ │ │ ├── colorable_windows.go │ │ │ ├── go.test.sh │ │ │ └── noncolorable.go │ │ ├── go-isatty/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── doc.go │ │ │ ├── go.test.sh │ │ │ ├── isatty_bsd.go │ │ │ ├── isatty_others.go │ │ │ ├── isatty_plan9.go │ │ │ ├── isatty_solaris.go │ │ │ ├── isatty_tcgets.go │ │ │ └── isatty_windows.go │ │ ├── go-localereader/ │ │ │ ├── README.md │ │ │ ├── localereader.go │ │ │ ├── localereader_unix.go │ │ │ └── localereader_windows.go │ │ ├── go-runewidth/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── runewidth.go │ │ │ ├── runewidth_appengine.go │ │ │ ├── runewidth_js.go │ │ │ ├── runewidth_posix.go │ │ │ ├── runewidth_table.go │ │ │ └── runewidth_windows.go │ │ └── go-shellwords/ │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── go.test.sh │ │ ├── shellwords.go │ │ ├── util_posix.go │ │ └── util_windows.go │ ├── mdlayher/ │ │ ├── genetlink/ │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── conn.go │ │ │ ├── doc.go │ │ │ ├── family.go │ │ │ ├── family_linux.go │ │ │ ├── family_others.go │ │ │ ├── fuzz.go │ │ │ └── message.go │ │ ├── netlink/ │ │ │ ├── .gitignore │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── align.go │ │ │ ├── attribute.go │ │ │ ├── conn.go │ │ │ ├── conn_linux.go │ │ │ ├── conn_others.go │ │ │ ├── debug.go │ │ │ ├── doc.go │ │ │ ├── errors.go │ │ │ ├── fuzz.go │ │ │ ├── message.go │ │ │ ├── nlenc/ │ │ │ │ ├── doc.go │ │ │ │ ├── int.go │ │ │ │ └── string.go │ │ │ └── nltest/ │ │ │ ├── errors_others.go │ │ │ ├── errors_unix.go │ │ │ └── nltest.go │ │ ├── sdnotify/ │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ └── sdnotify.go │ │ └── socket/ │ │ ├── CHANGELOG.md │ │ ├── LICENSE.md │ │ ├── README.md │ │ ├── accept.go │ │ ├── accept4.go │ │ ├── conn.go │ │ ├── conn_linux.go │ │ ├── doc.go │ │ ├── netns_linux.go │ │ ├── netns_others.go │ │ ├── setbuffer_linux.go │ │ ├── setbuffer_others.go │ │ ├── typ_cloexec_nonblock.go │ │ └── typ_none.go │ ├── mgutz/ │ │ └── ansi/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── ansi.go │ │ ├── doc.go │ │ └── print.go │ ├── miekg/ │ │ └── dns/ │ │ ├── .codecov.yml │ │ ├── .gitignore │ │ ├── AUTHORS │ │ ├── CODEOWNERS │ │ ├── CONTRIBUTORS │ │ ├── COPYRIGHT │ │ ├── LICENSE │ │ ├── Makefile.fuzz │ │ ├── Makefile.release │ │ ├── README.md │ │ ├── acceptfunc.go │ │ ├── client.go │ │ ├── clientconfig.go │ │ ├── dane.go │ │ ├── defaults.go │ │ ├── dns.go │ │ ├── dnssec.go │ │ ├── dnssec_keygen.go │ │ ├── dnssec_keyscan.go │ │ ├── dnssec_privkey.go │ │ ├── doc.go │ │ ├── duplicate.go │ │ ├── edns.go │ │ ├── format.go │ │ ├── fuzz.go │ │ ├── generate.go │ │ ├── hash.go │ │ ├── labels.go │ │ ├── listen_no_reuseport.go │ │ ├── listen_reuseport.go │ │ ├── msg.go │ │ ├── msg_helpers.go │ │ ├── msg_truncate.go │ │ ├── nsecx.go │ │ ├── privaterr.go │ │ ├── reverse.go │ │ ├── sanitize.go │ │ ├── scan.go │ │ ├── scan_rr.go │ │ ├── serve_mux.go │ │ ├── server.go │ │ ├── sig0.go │ │ ├── smimea.go │ │ ├── svcb.go │ │ ├── tlsa.go │ │ ├── tools.go │ │ ├── tsig.go │ │ ├── types.go │ │ ├── udp.go │ │ ├── udp_windows.go │ │ ├── update.go │ │ ├── version.go │ │ ├── xfr.go │ │ ├── zduplicate.go │ │ ├── zmsg.go │ │ └── ztypes.go │ ├── mitchellh/ │ │ ├── go-homedir/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── homedir.go │ │ ├── go-ps/ │ │ │ ├── .gitignore │ │ │ ├── .travis.yml │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── Vagrantfile │ │ │ ├── process.go │ │ │ ├── process_darwin.go │ │ │ ├── process_freebsd.go │ │ │ ├── process_linux.go │ │ │ ├── process_solaris.go │ │ │ ├── process_unix.go │ │ │ └── process_windows.go │ │ ├── go-wordwrap/ │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ └── wordwrap.go │ │ └── hashstructure/ │ │ └── v2/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── errors.go │ │ ├── hashstructure.go │ │ └── include.go │ ├── moby/ │ │ ├── buildkit/ │ │ │ ├── AUTHORS │ │ │ ├── LICENSE │ │ │ ├── api/ │ │ │ │ ├── services/ │ │ │ │ │ └── control/ │ │ │ │ │ ├── control.pb.go │ │ │ │ │ ├── control.proto │ │ │ │ │ ├── control_grpc.pb.go │ │ │ │ │ └── control_vtproto.pb.go │ │ │ │ └── types/ │ │ │ │ ├── worker.pb.go │ │ │ │ ├── worker.proto │ │ │ │ └── worker_vtproto.pb.go │ │ │ ├── client/ │ │ │ │ ├── build.go │ │ │ │ ├── buildid/ │ │ │ │ │ └── metadata.go │ │ │ │ ├── client.go │ │ │ │ ├── connhelper/ │ │ │ │ │ └── connhelper.go │ │ │ │ ├── diskusage.go │ │ │ │ ├── exporters.go │ │ │ │ ├── filter.go │ │ │ │ ├── graph.go │ │ │ │ ├── info.go │ │ │ │ ├── llb/ │ │ │ │ │ ├── async.go │ │ │ │ │ ├── definition.go │ │ │ │ │ ├── diff.go │ │ │ │ │ ├── exec.go │ │ │ │ │ ├── fileop.go │ │ │ │ │ ├── marshal.go │ │ │ │ │ ├── merge.go │ │ │ │ │ ├── meta.go │ │ │ │ │ ├── resolver.go │ │ │ │ │ ├── source.go │ │ │ │ │ ├── sourcemap.go │ │ │ │ │ ├── sourceresolver/ │ │ │ │ │ │ ├── imageresolver.go │ │ │ │ │ │ └── types.go │ │ │ │ │ └── state.go │ │ │ │ ├── ociindex/ │ │ │ │ │ └── ociindex.go │ │ │ │ ├── prune.go │ │ │ │ ├── solve.go │ │ │ │ ├── status.go │ │ │ │ └── workers.go │ │ │ ├── errdefs/ │ │ │ │ ├── internal.go │ │ │ │ ├── internal_linux.go │ │ │ │ └── internal_nolinux.go │ │ │ ├── exporter/ │ │ │ │ ├── containerimage/ │ │ │ │ │ └── exptypes/ │ │ │ │ │ ├── annotations.go │ │ │ │ │ ├── keys.go │ │ │ │ │ ├── parse.go │ │ │ │ │ └── types.go │ │ │ │ └── exptypes/ │ │ │ │ └── keys.go │ │ │ ├── frontend/ │ │ │ │ ├── dockerfile/ │ │ │ │ │ ├── command/ │ │ │ │ │ │ └── command.go │ │ │ │ │ ├── parser/ │ │ │ │ │ │ ├── directives.go │ │ │ │ │ │ ├── errors.go │ │ │ │ │ │ ├── line_parsers.go │ │ │ │ │ │ ├── parser.go │ │ │ │ │ │ └── split_command.go │ │ │ │ │ └── shell/ │ │ │ │ │ ├── envVarTest │ │ │ │ │ ├── equal_env_unix.go │ │ │ │ │ ├── equal_env_windows.go │ │ │ │ │ ├── lex.go │ │ │ │ │ └── wordsTest │ │ │ │ └── gateway/ │ │ │ │ ├── client/ │ │ │ │ │ ├── attestation.go │ │ │ │ │ └── client.go │ │ │ │ ├── grpcclient/ │ │ │ │ │ └── client.go │ │ │ │ └── pb/ │ │ │ │ ├── caps.go │ │ │ │ ├── exit.go │ │ │ │ ├── gateway.pb.go │ │ │ │ ├── gateway.proto │ │ │ │ ├── gateway_grpc.pb.go │ │ │ │ └── gateway_vtproto.pb.go │ │ │ ├── identity/ │ │ │ │ └── randomid.go │ │ │ ├── session/ │ │ │ │ ├── auth/ │ │ │ │ │ ├── auth.go │ │ │ │ │ ├── auth.pb.go │ │ │ │ │ ├── auth.proto │ │ │ │ │ ├── auth_grpc.pb.go │ │ │ │ │ ├── auth_vtproto.pb.go │ │ │ │ │ └── authprovider/ │ │ │ │ │ ├── authconfig.go │ │ │ │ │ ├── authprovider.go │ │ │ │ │ └── tokenseed.go │ │ │ │ ├── content/ │ │ │ │ │ ├── attachable.go │ │ │ │ │ └── caller.go │ │ │ │ ├── filesync/ │ │ │ │ │ ├── diffcopy.go │ │ │ │ │ ├── diffcopy_unix.go │ │ │ │ │ ├── diffcopy_windows.go │ │ │ │ │ ├── filesync.go │ │ │ │ │ ├── filesync.pb.go │ │ │ │ │ ├── filesync.proto │ │ │ │ │ ├── filesync_grpc.pb.go │ │ │ │ │ └── filesync_vtproto.pb.go │ │ │ │ ├── group.go │ │ │ │ ├── grpc.go │ │ │ │ ├── grpchijack/ │ │ │ │ │ ├── dial.go │ │ │ │ │ └── hijack.go │ │ │ │ ├── manager.go │ │ │ │ └── session.go │ │ │ ├── solver/ │ │ │ │ ├── pb/ │ │ │ │ │ ├── attr.go │ │ │ │ │ ├── caps.go │ │ │ │ │ ├── const.go │ │ │ │ │ ├── json.go │ │ │ │ │ ├── ops.go │ │ │ │ │ ├── ops.pb.go │ │ │ │ │ ├── ops.proto │ │ │ │ │ ├── ops_vtproto.pb.go │ │ │ │ │ └── platform.go │ │ │ │ └── result/ │ │ │ │ ├── attestation.go │ │ │ │ └── result.go │ │ │ ├── source/ │ │ │ │ └── types/ │ │ │ │ └── types.go │ │ │ ├── sourcepolicy/ │ │ │ │ └── pb/ │ │ │ │ ├── json.go │ │ │ │ ├── policy.pb.go │ │ │ │ ├── policy.proto │ │ │ │ └── policy_vtproto.pb.go │ │ │ ├── util/ │ │ │ │ ├── apicaps/ │ │ │ │ │ ├── caps.go │ │ │ │ │ └── pb/ │ │ │ │ │ ├── caps.pb.go │ │ │ │ │ ├── caps.proto │ │ │ │ │ └── caps_vtproto.pb.go │ │ │ │ ├── appdefaults/ │ │ │ │ │ ├── appdefaults.go │ │ │ │ │ ├── appdefaults_linux.go │ │ │ │ │ ├── appdefaults_unix.go │ │ │ │ │ ├── appdefaults_unix_nolinux.go │ │ │ │ │ └── appdefaults_windows.go │ │ │ │ ├── bklog/ │ │ │ │ │ └── log.go │ │ │ │ ├── contentutil/ │ │ │ │ │ ├── buffer.go │ │ │ │ │ ├── copy.go │ │ │ │ │ ├── fetcher.go │ │ │ │ │ ├── multiprovider.go │ │ │ │ │ ├── pusher.go │ │ │ │ │ ├── refs.go │ │ │ │ │ ├── source.go │ │ │ │ │ ├── storewithprovider.go │ │ │ │ │ └── types.go │ │ │ │ ├── flightcontrol/ │ │ │ │ │ ├── cached.go │ │ │ │ │ └── flightcontrol.go │ │ │ │ ├── gitutil/ │ │ │ │ │ ├── git_cli.go │ │ │ │ │ ├── git_cli_helpers.go │ │ │ │ │ ├── git_commit.go │ │ │ │ │ ├── git_ref.go │ │ │ │ │ └── git_url.go │ │ │ │ ├── gogo/ │ │ │ │ │ └── proto/ │ │ │ │ │ └── enum.go │ │ │ │ ├── grpcerrors/ │ │ │ │ │ ├── grpcerrors.go │ │ │ │ │ └── intercept.go │ │ │ │ ├── imageutil/ │ │ │ │ │ ├── config.go │ │ │ │ │ └── schema1.go │ │ │ │ ├── leaseutil/ │ │ │ │ │ └── manager.go │ │ │ │ ├── progress/ │ │ │ │ │ ├── multireader.go │ │ │ │ │ ├── multiwriter.go │ │ │ │ │ ├── progress.go │ │ │ │ │ ├── progressui/ │ │ │ │ │ │ ├── colors.go │ │ │ │ │ │ ├── display.go │ │ │ │ │ │ ├── init.go │ │ │ │ │ │ └── printer.go │ │ │ │ │ └── progresswriter/ │ │ │ │ │ ├── multiwriter.go │ │ │ │ │ ├── printer.go │ │ │ │ │ ├── progress.go │ │ │ │ │ ├── reset.go │ │ │ │ │ └── writer.go │ │ │ │ ├── resolver/ │ │ │ │ │ ├── limited/ │ │ │ │ │ │ └── group.go │ │ │ │ │ └── retryhandler/ │ │ │ │ │ └── retry.go │ │ │ │ ├── sshutil/ │ │ │ │ │ ├── keyscan.go │ │ │ │ │ └── scpurl.go │ │ │ │ ├── stack/ │ │ │ │ │ ├── compress.go │ │ │ │ │ ├── stack.go │ │ │ │ │ ├── stack.pb.go │ │ │ │ │ ├── stack.proto │ │ │ │ │ └── stack_vtproto.pb.go │ │ │ │ ├── system/ │ │ │ │ │ └── path.go │ │ │ │ └── tracing/ │ │ │ │ ├── grpcstats.go │ │ │ │ ├── multi_span_exporter.go │ │ │ │ ├── multispan.go │ │ │ │ ├── otlptracegrpc/ │ │ │ │ │ ├── client.go │ │ │ │ │ ├── connection.go │ │ │ │ │ └── errors.go │ │ │ │ └── tracing.go │ │ │ └── version/ │ │ │ ├── ua.go │ │ │ └── version.go │ │ ├── docker-image-spec/ │ │ │ ├── LICENSE │ │ │ └── specs-go/ │ │ │ └── v1/ │ │ │ └── image.go │ │ ├── locker/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── locker.go │ │ ├── patternmatcher/ │ │ │ ├── LICENSE │ │ │ ├── NOTICE │ │ │ ├── ignorefile/ │ │ │ │ └── ignorefile.go │ │ │ └── patternmatcher.go │ │ ├── spdystream/ │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── MAINTAINERS │ │ │ ├── NOTICE │ │ │ ├── README.md │ │ │ ├── connection.go │ │ │ ├── handlers.go │ │ │ ├── priority.go │ │ │ ├── spdy/ │ │ │ │ ├── dictionary.go │ │ │ │ ├── read.go │ │ │ │ ├── types.go │ │ │ │ └── write.go │ │ │ ├── stream.go │ │ │ └── utils.go │ │ └── sys/ │ │ ├── capability/ │ │ │ ├── .codespellrc │ │ │ ├── .golangci.yml │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── capability.go │ │ │ ├── capability_linux.go │ │ │ ├── capability_noop.go │ │ │ ├── enum.go │ │ │ ├── enum_gen.go │ │ │ └── syscall_linux.go │ │ ├── mountinfo/ │ │ │ ├── LICENSE │ │ │ ├── doc.go │ │ │ ├── mounted_linux.go │ │ │ ├── mounted_unix.go │ │ │ ├── mountinfo.go │ │ │ ├── mountinfo_bsd.go │ │ │ ├── mountinfo_filters.go │ │ │ ├── mountinfo_freebsdlike.go │ │ │ ├── mountinfo_linux.go │ │ │ ├── mountinfo_openbsd.go │ │ │ ├── mountinfo_unsupported.go │ │ │ └── mountinfo_windows.go │ │ ├── signal/ │ │ │ ├── LICENSE │ │ │ ├── signal.go │ │ │ ├── signal_darwin.go │ │ │ ├── signal_freebsd.go │ │ │ ├── signal_linux.go │ │ │ ├── signal_linux_mipsx.go │ │ │ ├── signal_unix.go │ │ │ ├── signal_unsupported.go │ │ │ └── signal_windows.go │ │ └── user/ │ │ ├── LICENSE │ │ ├── lookup_unix.go │ │ ├── user.go │ │ └── user_fuzzer.go │ ├── modern-go/ │ │ └── concurrent/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── executor.go │ │ ├── go_above_19.go │ │ ├── go_below_19.go │ │ ├── log.go │ │ ├── test.sh │ │ └── unbounded_executor.go │ ├── morikuni/ │ │ └── aec/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── aec.go │ │ ├── ansi.go │ │ ├── builder.go │ │ └── sgr.go │ ├── muesli/ │ │ ├── ansi/ │ │ │ ├── .gitignore │ │ │ ├── .golangci.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── ansi.go │ │ │ ├── buffer.go │ │ │ ├── compressor/ │ │ │ │ └── writer.go │ │ │ └── writer.go │ │ ├── cancelreader/ │ │ │ ├── .gitignore │ │ │ ├── .golangci-soft.yml │ │ │ ├── .golangci.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── cancelreader.go │ │ │ ├── cancelreader_bsd.go │ │ │ ├── cancelreader_default.go │ │ │ ├── cancelreader_linux.go │ │ │ ├── cancelreader_select.go │ │ │ ├── cancelreader_unix.go │ │ │ └── cancelreader_windows.go │ │ └── termenv/ │ │ ├── .gitignore │ │ ├── .golangci-soft.yml │ │ ├── .golangci.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── ansi_compat.md │ │ ├── ansicolors.go │ │ ├── color.go │ │ ├── constants_linux.go │ │ ├── constants_solaris.go │ │ ├── constants_unix.go │ │ ├── constants_zos.go │ │ ├── copy.go │ │ ├── hyperlink.go │ │ ├── notification.go │ │ ├── output.go │ │ ├── profile.go │ │ ├── screen.go │ │ ├── style.go │ │ ├── templatehelper.go │ │ ├── termenv.go │ │ ├── termenv_other.go │ │ ├── termenv_posix.go │ │ ├── termenv_solaris.go │ │ ├── termenv_unix.go │ │ └── termenv_windows.go │ ├── munnerz/ │ │ └── goautoneg/ │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.txt │ │ └── autoneg.go │ ├── mxk/ │ │ └── go-flowrate/ │ │ ├── LICENSE │ │ └── flowrate/ │ │ ├── flowrate.go │ │ ├── io.go │ │ └── util.go │ ├── olekukonko/ │ │ └── tablewriter/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE.md │ │ ├── README.md │ │ ├── csv.go │ │ ├── table.go │ │ ├── table_with_color.go │ │ ├── util.go │ │ └── wrap.go │ ├── onsi/ │ │ ├── ginkgo/ │ │ │ └── v2/ │ │ │ ├── .gitignore │ │ │ ├── CHANGELOG.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── RELEASING.md │ │ │ ├── config/ │ │ │ │ └── deprecated.go │ │ │ ├── core_dsl.go │ │ │ ├── decorator_dsl.go │ │ │ ├── deprecated_dsl.go │ │ │ ├── formatter/ │ │ │ │ ├── colorable_others.go │ │ │ │ ├── colorable_windows.go │ │ │ │ └── formatter.go │ │ │ ├── ginkgo/ │ │ │ │ ├── build/ │ │ │ │ │ └── build_command.go │ │ │ │ ├── command/ │ │ │ │ │ ├── abort.go │ │ │ │ │ ├── command.go │ │ │ │ │ └── program.go │ │ │ │ ├── generators/ │ │ │ │ │ ├── boostrap_templates.go │ │ │ │ │ ├── bootstrap_command.go │ │ │ │ │ ├── generate_command.go │ │ │ │ │ ├── generate_templates.go │ │ │ │ │ └── generators_common.go │ │ │ │ ├── internal/ │ │ │ │ │ ├── compile.go │ │ │ │ │ ├── gocovmerge.go │ │ │ │ │ ├── profiles_and_reports.go │ │ │ │ │ ├── run.go │ │ │ │ │ ├── test_suite.go │ │ │ │ │ ├── utils.go │ │ │ │ │ └── verify_version.go │ │ │ │ ├── labels/ │ │ │ │ │ └── labels_command.go │ │ │ │ ├── main.go │ │ │ │ ├── outline/ │ │ │ │ │ ├── ginkgo.go │ │ │ │ │ ├── import.go │ │ │ │ │ ├── outline.go │ │ │ │ │ └── outline_command.go │ │ │ │ ├── run/ │ │ │ │ │ └── run_command.go │ │ │ │ ├── unfocus/ │ │ │ │ │ └── unfocus_command.go │ │ │ │ └── watch/ │ │ │ │ ├── delta.go │ │ │ │ ├── delta_tracker.go │ │ │ │ ├── dependencies.go │ │ │ │ ├── package_hash.go │ │ │ │ ├── package_hashes.go │ │ │ │ ├── suite.go │ │ │ │ └── watch_command.go │ │ │ ├── ginkgo_cli_dependencies.go │ │ │ ├── ginkgo_t_dsl.go │ │ │ ├── internal/ │ │ │ │ ├── counter.go │ │ │ │ ├── failer.go │ │ │ │ ├── focus.go │ │ │ │ ├── global/ │ │ │ │ │ └── init.go │ │ │ │ ├── group.go │ │ │ │ ├── interrupt_handler/ │ │ │ │ │ ├── interrupt_handler.go │ │ │ │ │ ├── sigquit_swallower_unix.go │ │ │ │ │ └── sigquit_swallower_windows.go │ │ │ │ ├── node.go │ │ │ │ ├── ordering.go │ │ │ │ ├── output_interceptor.go │ │ │ │ ├── output_interceptor_unix.go │ │ │ │ ├── output_interceptor_wasm.go │ │ │ │ ├── output_interceptor_win.go │ │ │ │ ├── parallel_support/ │ │ │ │ │ ├── client_server.go │ │ │ │ │ ├── http_client.go │ │ │ │ │ ├── http_server.go │ │ │ │ │ ├── rpc_client.go │ │ │ │ │ ├── rpc_server.go │ │ │ │ │ └── server_handler.go │ │ │ │ ├── progress_report.go │ │ │ │ ├── progress_report_bsd.go │ │ │ │ ├── progress_report_unix.go │ │ │ │ ├── progress_report_wasm.go │ │ │ │ ├── progress_report_win.go │ │ │ │ ├── progress_reporter_manager.go │ │ │ │ ├── report_entry.go │ │ │ │ ├── spec.go │ │ │ │ ├── spec_context.go │ │ │ │ ├── suite.go │ │ │ │ ├── testingtproxy/ │ │ │ │ │ └── testing_t_proxy.go │ │ │ │ ├── tree.go │ │ │ │ └── writer.go │ │ │ ├── reporters/ │ │ │ │ ├── default_reporter.go │ │ │ │ ├── deprecated_reporter.go │ │ │ │ ├── json_report.go │ │ │ │ ├── junit_report.go │ │ │ │ ├── reporter.go │ │ │ │ └── teamcity_report.go │ │ │ ├── reporting_dsl.go │ │ │ ├── table_dsl.go │ │ │ └── types/ │ │ │ ├── code_location.go │ │ │ ├── config.go │ │ │ ├── deprecated_types.go │ │ │ ├── deprecation_support.go │ │ │ ├── enum_support.go │ │ │ ├── errors.go │ │ │ ├── file_filter.go │ │ │ ├── flags.go │ │ │ ├── label_filter.go │ │ │ ├── report_entry.go │ │ │ ├── types.go │ │ │ └── version.go │ │ └── gomega/ │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── RELEASING.md │ │ ├── format/ │ │ │ └── format.go │ │ ├── ghttp/ │ │ │ ├── handlers.go │ │ │ └── test_server.go │ │ ├── gomega_dsl.go │ │ ├── internal/ │ │ │ ├── assertion.go │ │ │ ├── async_assertion.go │ │ │ ├── duration_bundle.go │ │ │ ├── gomega.go │ │ │ ├── gutil/ │ │ │ │ ├── post_ioutil.go │ │ │ │ └── using_ioutil.go │ │ │ ├── polling_signal_error.go │ │ │ └── vetoptdesc.go │ │ ├── matchers/ │ │ │ ├── and.go │ │ │ ├── assignable_to_type_of_matcher.go │ │ │ ├── attributes_slice.go │ │ │ ├── be_a_directory.go │ │ │ ├── be_a_regular_file.go │ │ │ ├── be_an_existing_file.go │ │ │ ├── be_closed_matcher.go │ │ │ ├── be_comparable_to_matcher.go │ │ │ ├── be_element_of_matcher.go │ │ │ ├── be_empty_matcher.go │ │ │ ├── be_equivalent_to_matcher.go │ │ │ ├── be_false_matcher.go │ │ │ ├── be_identical_to.go │ │ │ ├── be_key_of_matcher.go │ │ │ ├── be_nil_matcher.go │ │ │ ├── be_numerically_matcher.go │ │ │ ├── be_sent_matcher.go │ │ │ ├── be_temporally_matcher.go │ │ │ ├── be_true_matcher.go │ │ │ ├── be_zero_matcher.go │ │ │ ├── consist_of.go │ │ │ ├── contain_element_matcher.go │ │ │ ├── contain_elements_matcher.go │ │ │ ├── contain_substring_matcher.go │ │ │ ├── equal_matcher.go │ │ │ ├── have_cap_matcher.go │ │ │ ├── have_each_matcher.go │ │ │ ├── have_exact_elements.go │ │ │ ├── have_existing_field_matcher.go │ │ │ ├── have_field.go │ │ │ ├── have_http_body_matcher.go │ │ │ ├── have_http_header_with_value_matcher.go │ │ │ ├── have_http_status_matcher.go │ │ │ ├── have_key_matcher.go │ │ │ ├── have_key_with_value_matcher.go │ │ │ ├── have_len_matcher.go │ │ │ ├── have_occurred_matcher.go │ │ │ ├── have_prefix_matcher.go │ │ │ ├── have_suffix_matcher.go │ │ │ ├── have_value.go │ │ │ ├── match_error_matcher.go │ │ │ ├── match_json_matcher.go │ │ │ ├── match_regexp_matcher.go │ │ │ ├── match_xml_matcher.go │ │ │ ├── match_yaml_matcher.go │ │ │ ├── not.go │ │ │ ├── or.go │ │ │ ├── panic_matcher.go │ │ │ ├── receive_matcher.go │ │ │ ├── satisfy_matcher.go │ │ │ ├── semi_structured_data_support.go │ │ │ ├── succeed_matcher.go │ │ │ ├── support/ │ │ │ │ └── goraph/ │ │ │ │ ├── bipartitegraph/ │ │ │ │ │ ├── bipartitegraph.go │ │ │ │ │ └── bipartitegraphmatching.go │ │ │ │ ├── edge/ │ │ │ │ │ └── edge.go │ │ │ │ ├── node/ │ │ │ │ │ └── node.go │ │ │ │ └── util/ │ │ │ │ └── util.go │ │ │ ├── type_support.go │ │ │ └── with_transform.go │ │ ├── matchers.go │ │ └── types/ │ │ └── types.go │ ├── opencontainers/ │ │ ├── go-digest/ │ │ │ ├── .mailmap │ │ │ ├── .pullapprove.yml │ │ │ ├── .travis.yml │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE │ │ │ ├── LICENSE.docs │ │ │ ├── MAINTAINERS │ │ │ ├── README.md │ │ │ ├── algorithm.go │ │ │ ├── digest.go │ │ │ ├── digester.go │ │ │ ├── doc.go │ │ │ └── verifiers.go │ │ ├── image-spec/ │ │ │ ├── LICENSE │ │ │ └── specs-go/ │ │ │ ├── v1/ │ │ │ │ ├── annotations.go │ │ │ │ ├── config.go │ │ │ │ ├── descriptor.go │ │ │ │ ├── index.go │ │ │ │ ├── layout.go │ │ │ │ ├── manifest.go │ │ │ │ └── mediatype.go │ │ │ ├── version.go │ │ │ └── versioned.go │ │ └── runtime-spec/ │ │ ├── LICENSE │ │ └── specs-go/ │ │ ├── config.go │ │ ├── state.go │ │ └── version.go │ ├── pierrec/ │ │ └── lz4/ │ │ └── v4/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── compressing_reader.go │ │ ├── internal/ │ │ │ ├── lz4block/ │ │ │ │ ├── block.go │ │ │ │ ├── blocks.go │ │ │ │ ├── decode_amd64.s │ │ │ │ ├── decode_arm.s │ │ │ │ ├── decode_arm64.s │ │ │ │ ├── decode_asm.go │ │ │ │ └── decode_other.go │ │ │ ├── lz4errors/ │ │ │ │ └── errors.go │ │ │ ├── lz4stream/ │ │ │ │ ├── block.go │ │ │ │ ├── frame.go │ │ │ │ └── frame_gen.go │ │ │ └── xxh32/ │ │ │ ├── xxh32zero.go │ │ │ ├── xxh32zero_arm.go │ │ │ ├── xxh32zero_arm.s │ │ │ └── xxh32zero_other.go │ │ ├── lz4.go │ │ ├── options.go │ │ ├── options_gen.go │ │ ├── reader.go │ │ ├── state.go │ │ ├── state_gen.go │ │ └── writer.go │ ├── pkg/ │ │ ├── errors/ │ │ │ ├── .gitignore │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── appveyor.yml │ │ │ ├── errors.go │ │ │ ├── go113.go │ │ │ └── stack.go │ │ └── sftp/ │ │ ├── .gitignore │ │ ├── CONTRIBUTORS │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── allocator.go │ │ ├── attrs.go │ │ ├── attrs_stubs.go │ │ ├── attrs_unix.go │ │ ├── client.go │ │ ├── conn.go │ │ ├── debug.go │ │ ├── fuzz.go │ │ ├── internal/ │ │ │ └── encoding/ │ │ │ └── ssh/ │ │ │ └── filexfer/ │ │ │ ├── attrs.go │ │ │ ├── buffer.go │ │ │ ├── extended_packets.go │ │ │ ├── extensions.go │ │ │ ├── filexfer.go │ │ │ ├── fx.go │ │ │ ├── fxp.go │ │ │ ├── handle_packets.go │ │ │ ├── init_packets.go │ │ │ ├── open_packets.go │ │ │ ├── packets.go │ │ │ ├── path_packets.go │ │ │ ├── permissions.go │ │ │ └── response_packets.go │ │ ├── ls_formatting.go │ │ ├── ls_plan9.go │ │ ├── ls_stub.go │ │ ├── ls_unix.go │ │ ├── match.go │ │ ├── packet-manager.go │ │ ├── packet-typing.go │ │ ├── packet.go │ │ ├── pool.go │ │ ├── release.go │ │ ├── request-attrs.go │ │ ├── request-errors.go │ │ ├── request-example.go │ │ ├── request-interfaces.go │ │ ├── request-plan9.go │ │ ├── request-readme.md │ │ ├── request-server.go │ │ ├── request-unix.go │ │ ├── request.go │ │ ├── request_windows.go │ │ ├── server.go │ │ ├── server_plan9.go │ │ ├── server_statvfs_darwin.go │ │ ├── server_statvfs_impl.go │ │ ├── server_statvfs_linux.go │ │ ├── server_statvfs_plan9.go │ │ ├── server_statvfs_stubs.go │ │ ├── server_unix.go │ │ ├── server_windows.go │ │ ├── sftp.go │ │ ├── stat_plan9.go │ │ ├── stat_posix.go │ │ ├── syscall_fixed.go │ │ └── syscall_good.go │ ├── planetscale/ │ │ └── vtprotobuf/ │ │ ├── LICENSE │ │ ├── protohelpers/ │ │ │ └── protohelpers.go │ │ ├── types/ │ │ │ └── known/ │ │ │ └── timestamppb/ │ │ │ └── timestamp_vtproto.pb.go │ │ └── vtproto/ │ │ └── ext.pb.go │ ├── prometheus/ │ │ ├── client_golang/ │ │ │ ├── LICENSE │ │ │ ├── NOTICE │ │ │ ├── internal/ │ │ │ │ └── github.com/ │ │ │ │ └── golang/ │ │ │ │ └── gddo/ │ │ │ │ ├── LICENSE │ │ │ │ └── httputil/ │ │ │ │ ├── header/ │ │ │ │ │ └── header.go │ │ │ │ └── negotiate.go │ │ │ └── prometheus/ │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── build_info_collector.go │ │ │ ├── collector.go │ │ │ ├── collectors/ │ │ │ │ ├── collectors.go │ │ │ │ ├── dbstats_collector.go │ │ │ │ ├── expvar_collector.go │ │ │ │ ├── go_collector_go116.go │ │ │ │ ├── go_collector_latest.go │ │ │ │ └── process_collector.go │ │ │ ├── counter.go │ │ │ ├── desc.go │ │ │ ├── doc.go │ │ │ ├── expvar_collector.go │ │ │ ├── fnv.go │ │ │ ├── gauge.go │ │ │ ├── get_pid.go │ │ │ ├── get_pid_gopherjs.go │ │ │ ├── go_collector.go │ │ │ ├── go_collector_go116.go │ │ │ ├── go_collector_latest.go │ │ │ ├── histogram.go │ │ │ ├── internal/ │ │ │ │ ├── almost_equal.go │ │ │ │ ├── difflib.go │ │ │ │ ├── go_collector_options.go │ │ │ │ ├── go_runtime_metrics.go │ │ │ │ └── metric.go │ │ │ ├── labels.go │ │ │ ├── metric.go │ │ │ ├── num_threads.go │ │ │ ├── num_threads_gopherjs.go │ │ │ ├── observer.go │ │ │ ├── process_collector.go │ │ │ ├── process_collector_js.go │ │ │ ├── process_collector_other.go │ │ │ ├── process_collector_wasip1.go │ │ │ ├── process_collector_windows.go │ │ │ ├── promhttp/ │ │ │ │ ├── delegator.go │ │ │ │ ├── http.go │ │ │ │ ├── instrument_client.go │ │ │ │ ├── instrument_server.go │ │ │ │ └── option.go │ │ │ ├── registry.go │ │ │ ├── summary.go │ │ │ ├── testutil/ │ │ │ │ ├── lint.go │ │ │ │ ├── promlint/ │ │ │ │ │ ├── problem.go │ │ │ │ │ ├── promlint.go │ │ │ │ │ ├── validation.go │ │ │ │ │ └── validations/ │ │ │ │ │ ├── counter_validations.go │ │ │ │ │ ├── duplicate_validations.go │ │ │ │ │ ├── generic_name_validations.go │ │ │ │ │ ├── help_validations.go │ │ │ │ │ ├── histogram_validations.go │ │ │ │ │ └── units.go │ │ │ │ └── testutil.go │ │ │ ├── timer.go │ │ │ ├── untyped.go │ │ │ ├── value.go │ │ │ ├── vec.go │ │ │ ├── vnext.go │ │ │ └── wrap.go │ │ ├── client_model/ │ │ │ ├── LICENSE │ │ │ ├── NOTICE │ │ │ └── go/ │ │ │ └── metrics.pb.go │ │ ├── common/ │ │ │ ├── LICENSE │ │ │ ├── NOTICE │ │ │ ├── expfmt/ │ │ │ │ ├── decode.go │ │ │ │ ├── encode.go │ │ │ │ ├── expfmt.go │ │ │ │ ├── fuzz.go │ │ │ │ ├── openmetrics_create.go │ │ │ │ ├── text_create.go │ │ │ │ └── text_parse.go │ │ │ └── model/ │ │ │ ├── alert.go │ │ │ ├── fingerprinting.go │ │ │ ├── fnv.go │ │ │ ├── labels.go │ │ │ ├── labelset.go │ │ │ ├── labelset_string.go │ │ │ ├── metadata.go │ │ │ ├── metric.go │ │ │ ├── model.go │ │ │ ├── signature.go │ │ │ ├── silence.go │ │ │ ├── time.go │ │ │ ├── value.go │ │ │ ├── value_float.go │ │ │ ├── value_histogram.go │ │ │ └── value_type.go │ │ └── procfs/ │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── CODE_OF_CONDUCT.md │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── MAINTAINERS.md │ │ ├── Makefile │ │ ├── Makefile.common │ │ ├── NOTICE │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── arp.go │ │ ├── buddyinfo.go │ │ ├── cmdline.go │ │ ├── cpuinfo.go │ │ ├── cpuinfo_armx.go │ │ ├── cpuinfo_loong64.go │ │ ├── cpuinfo_mipsx.go │ │ ├── cpuinfo_others.go │ │ ├── cpuinfo_ppcx.go │ │ ├── cpuinfo_riscvx.go │ │ ├── cpuinfo_s390x.go │ │ ├── cpuinfo_x86.go │ │ ├── crypto.go │ │ ├── doc.go │ │ ├── fs.go │ │ ├── fs_statfs_notype.go │ │ ├── fs_statfs_type.go │ │ ├── fscache.go │ │ ├── internal/ │ │ │ ├── fs/ │ │ │ │ └── fs.go │ │ │ └── util/ │ │ │ ├── parse.go │ │ │ ├── readfile.go │ │ │ ├── sysreadfile.go │ │ │ ├── sysreadfile_compat.go │ │ │ └── valueparser.go │ │ ├── ipvs.go │ │ ├── kernel_random.go │ │ ├── loadavg.go │ │ ├── mdstat.go │ │ ├── meminfo.go │ │ ├── mountinfo.go │ │ ├── mountstats.go │ │ ├── net_conntrackstat.go │ │ ├── net_dev.go │ │ ├── net_ip_socket.go │ │ ├── net_protocols.go │ │ ├── net_route.go │ │ ├── net_sockstat.go │ │ ├── net_softnet.go │ │ ├── net_tcp.go │ │ ├── net_tls_stat.go │ │ ├── net_udp.go │ │ ├── net_unix.go │ │ ├── net_wireless.go │ │ ├── net_xfrm.go │ │ ├── netstat.go │ │ ├── proc.go │ │ ├── proc_cgroup.go │ │ ├── proc_cgroups.go │ │ ├── proc_environ.go │ │ ├── proc_fdinfo.go │ │ ├── proc_interrupts.go │ │ ├── proc_io.go │ │ ├── proc_limits.go │ │ ├── proc_maps.go │ │ ├── proc_netstat.go │ │ ├── proc_ns.go │ │ ├── proc_psi.go │ │ ├── proc_smaps.go │ │ ├── proc_snmp.go │ │ ├── proc_snmp6.go │ │ ├── proc_stat.go │ │ ├── proc_status.go │ │ ├── proc_sys.go │ │ ├── schedstat.go │ │ ├── slab.go │ │ ├── softirqs.go │ │ ├── stat.go │ │ ├── swaps.go │ │ ├── thread.go │ │ ├── ttar │ │ ├── vm.go │ │ └── zoneinfo.go │ ├── prometheus-community/ │ │ └── pro-bing/ │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── .goreleaser.yaml │ │ ├── CODE_OF_CONDUCT.md │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── MAINTAINERS.md │ │ ├── Makefile │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── http.go │ │ ├── logger.go │ │ ├── packetconn.go │ │ ├── ping.go │ │ ├── utils_linux.go │ │ ├── utils_other.go │ │ └── utils_windows.go │ ├── ramr/ │ │ └── go-reaper/ │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ └── reaper.go │ ├── rhysd/ │ │ └── go-github-selfupdate/ │ │ ├── LICENSE │ │ └── selfupdate/ │ │ ├── detect.go │ │ ├── doc.go │ │ ├── log.go │ │ ├── release.go │ │ ├── uncompress.go │ │ ├── update.go │ │ ├── updater.go │ │ └── validate.go │ ├── rivo/ │ │ └── uniseg/ │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── doc.go │ │ ├── eastasianwidth.go │ │ ├── emojipresentation.go │ │ ├── gen_breaktest.go │ │ ├── gen_properties.go │ │ ├── grapheme.go │ │ ├── graphemeproperties.go │ │ ├── graphemerules.go │ │ ├── line.go │ │ ├── lineproperties.go │ │ ├── linerules.go │ │ ├── properties.go │ │ ├── sentence.go │ │ ├── sentenceproperties.go │ │ ├── sentencerules.go │ │ ├── step.go │ │ ├── width.go │ │ ├── word.go │ │ ├── wordproperties.go │ │ └── wordrules.go │ ├── safchain/ │ │ └── ethtool/ │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── ethtool.go │ │ ├── ethtool_cmd.go │ │ └── ethtool_msglvl.go │ ├── secure-systems-lab/ │ │ └── go-securesystemslib/ │ │ ├── LICENSE │ │ ├── cjson/ │ │ │ └── canonicaljson.go │ │ ├── dsse/ │ │ │ ├── envelope.go │ │ │ ├── sign.go │ │ │ ├── signerverifier.go │ │ │ └── verify.go │ │ └── signerverifier/ │ │ ├── ecdsa.go │ │ ├── ed25519.go │ │ ├── rsa.go │ │ ├── signerverifier.go │ │ └── utils.go │ ├── shibumi/ │ │ └── go-pathspec/ │ │ ├── .gitignore │ │ ├── GO-LICENSE │ │ ├── LICENSE │ │ ├── README.md │ │ └── gitignore.go │ ├── skratchdot/ │ │ └── open-golang/ │ │ ├── LICENSE │ │ └── open/ │ │ ├── exec.go │ │ ├── exec_darwin.go │ │ ├── exec_windows.go │ │ └── open.go │ ├── spf13/ │ │ ├── cobra/ │ │ │ ├── .gitignore │ │ │ ├── .golangci.yml │ │ │ ├── .mailmap │ │ │ ├── CONDUCT.md │ │ │ ├── CONTRIBUTING.md │ │ │ ├── LICENSE.txt │ │ │ ├── MAINTAINERS │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── active_help.go │ │ │ ├── args.go │ │ │ ├── bash_completions.go │ │ │ ├── bash_completionsV2.go │ │ │ ├── cobra.go │ │ │ ├── command.go │ │ │ ├── command_notwin.go │ │ │ ├── command_win.go │ │ │ ├── completions.go │ │ │ ├── fish_completions.go │ │ │ ├── flag_groups.go │ │ │ ├── powershell_completions.go │ │ │ ├── shell_completions.go │ │ │ └── zsh_completions.go │ │ └── pflag/ │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── bool.go │ │ ├── bool_slice.go │ │ ├── bytes.go │ │ ├── count.go │ │ ├── duration.go │ │ ├── duration_slice.go │ │ ├── flag.go │ │ ├── float32.go │ │ ├── float32_slice.go │ │ ├── float64.go │ │ ├── float64_slice.go │ │ ├── golangflag.go │ │ ├── int.go │ │ ├── int16.go │ │ ├── int32.go │ │ ├── int32_slice.go │ │ ├── int64.go │ │ ├── int64_slice.go │ │ ├── int8.go │ │ ├── int_slice.go │ │ ├── ip.go │ │ ├── ip_slice.go │ │ ├── ipmask.go │ │ ├── ipnet.go │ │ ├── string.go │ │ ├── string_array.go │ │ ├── string_slice.go │ │ ├── string_to_int.go │ │ ├── string_to_int64.go │ │ ├── string_to_string.go │ │ ├── uint.go │ │ ├── uint16.go │ │ ├── uint32.go │ │ ├── uint64.go │ │ ├── uint8.go │ │ └── uint_slice.go │ ├── tailscale/ │ │ ├── certstore/ │ │ │ ├── .appveyor.yml │ │ │ ├── LICENSE.md │ │ │ ├── README.md │ │ │ ├── certstore.go │ │ │ ├── certstore_darwin.go │ │ │ ├── certstore_linux.go │ │ │ ├── certstore_windows.go │ │ │ ├── crypt_strings_windows.go │ │ │ ├── syscall_windows.go │ │ │ └── zsyscall_windows.go │ │ ├── go-winio/ │ │ │ ├── .gitattributes │ │ │ ├── .gitignore │ │ │ ├── .golangci.yml │ │ │ ├── CODEOWNERS │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── SECURITY.md │ │ │ ├── backup.go │ │ │ ├── doc.go │ │ │ ├── ea.go │ │ │ ├── file.go │ │ │ ├── fileinfo.go │ │ │ ├── hvsock.go │ │ │ ├── internal/ │ │ │ │ ├── fs/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fs.go │ │ │ │ │ ├── security.go │ │ │ │ │ └── zsyscall_windows.go │ │ │ │ ├── socket/ │ │ │ │ │ ├── rawaddr.go │ │ │ │ │ ├── socket.go │ │ │ │ │ └── zsyscall_windows.go │ │ │ │ └── stringbuffer/ │ │ │ │ └── wstring.go │ │ │ ├── pipe.go │ │ │ ├── pkg/ │ │ │ │ └── guid/ │ │ │ │ ├── guid.go │ │ │ │ ├── guid_nonwindows.go │ │ │ │ ├── guid_windows.go │ │ │ │ └── variant_string.go │ │ │ ├── privilege.go │ │ │ ├── reparse.go │ │ │ ├── sd.go │ │ │ ├── syscall.go │ │ │ └── zsyscall_windows.go │ │ ├── golang-x-crypto/ │ │ │ ├── LICENSE │ │ │ ├── PATENTS │ │ │ ├── acme/ │ │ │ │ ├── acme.go │ │ │ │ ├── http.go │ │ │ │ ├── jws.go │ │ │ │ ├── rfc8555.go │ │ │ │ ├── types.go │ │ │ │ └── version_go112.go │ │ │ ├── internal/ │ │ │ │ └── poly1305/ │ │ │ │ ├── mac_noasm.go │ │ │ │ ├── poly1305.go │ │ │ │ ├── sum_amd64.go │ │ │ │ ├── sum_amd64.s │ │ │ │ ├── sum_generic.go │ │ │ │ ├── sum_ppc64le.go │ │ │ │ ├── sum_ppc64le.s │ │ │ │ ├── sum_s390x.go │ │ │ │ └── sum_s390x.s │ │ │ └── ssh/ │ │ │ ├── buffer.go │ │ │ ├── certs.go │ │ │ ├── channel.go │ │ │ ├── cipher.go │ │ │ ├── client.go │ │ │ ├── client_auth.go │ │ │ ├── common.go │ │ │ ├── connection.go │ │ │ ├── doc.go │ │ │ ├── handshake.go │ │ │ ├── internal/ │ │ │ │ └── bcrypt_pbkdf/ │ │ │ │ └── bcrypt_pbkdf.go │ │ │ ├── kex.go │ │ │ ├── keys.go │ │ │ ├── mac.go │ │ │ ├── messages.go │ │ │ ├── mux.go │ │ │ ├── server.go │ │ │ ├── session.go │ │ │ ├── ssh_gss.go │ │ │ ├── streamlocal.go │ │ │ ├── tcpip.go │ │ │ └── transport.go │ │ ├── goupnp/ │ │ │ ├── .gitignore │ │ │ ├── GUIDE.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── dcps/ │ │ │ │ └── internetgateway2/ │ │ │ │ ├── gen.go │ │ │ │ └── internetgateway2.go │ │ │ ├── device.go │ │ │ ├── goupnp.go │ │ │ ├── httpu/ │ │ │ │ ├── httpu.go │ │ │ │ ├── multiclient.go │ │ │ │ └── serve.go │ │ │ ├── network.go │ │ │ ├── scpd/ │ │ │ │ └── scpd.go │ │ │ ├── service_client.go │ │ │ ├── soap/ │ │ │ │ ├── soap.go │ │ │ │ └── types.go │ │ │ └── ssdp/ │ │ │ ├── registry.go │ │ │ └── ssdp.go │ │ ├── hujson/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── find.go │ │ │ ├── format.go │ │ │ ├── go.work │ │ │ ├── pack.go │ │ │ ├── parse.go │ │ │ ├── patch.go │ │ │ ├── standard.go │ │ │ └── types.go │ │ ├── netlink/ │ │ │ ├── .gitignore │ │ │ ├── .travis.yml │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── Makefile │ │ │ ├── README.md │ │ │ ├── addr.go │ │ │ ├── addr_linux.go │ │ │ ├── bpf_linux.go │ │ │ ├── bridge_linux.go │ │ │ ├── class.go │ │ │ ├── class_linux.go │ │ │ ├── conntrack_linux.go │ │ │ ├── conntrack_unspecified.go │ │ │ ├── devlink_linux.go │ │ │ ├── filter.go │ │ │ ├── filter_linux.go │ │ │ ├── fou.go │ │ │ ├── fou_linux.go │ │ │ ├── fou_unspecified.go │ │ │ ├── genetlink_linux.go │ │ │ ├── genetlink_unspecified.go │ │ │ ├── gtp_linux.go │ │ │ ├── handle_linux.go │ │ │ ├── handle_unspecified.go │ │ │ ├── inet_diag.go │ │ │ ├── ioctl_linux.go │ │ │ ├── ipset_linux.go │ │ │ ├── link.go │ │ │ ├── link_linux.go │ │ │ ├── link_tuntap_linux.go │ │ │ ├── neigh.go │ │ │ ├── neigh_linux.go │ │ │ ├── netlink.go │ │ │ ├── netlink_linux.go │ │ │ ├── netlink_unspecified.go │ │ │ ├── netns_linux.go │ │ │ ├── netns_unspecified.go │ │ │ ├── nl/ │ │ │ │ ├── addr_linux.go │ │ │ │ ├── bridge_linux.go │ │ │ │ ├── conntrack_linux.go │ │ │ │ ├── devlink_linux.go │ │ │ │ ├── genetlink_linux.go │ │ │ │ ├── ipset_linux.go │ │ │ │ ├── link_linux.go │ │ │ │ ├── lwt_linux.go │ │ │ │ ├── mpls_linux.go │ │ │ │ ├── nl_linux.go │ │ │ │ ├── nl_unspecified.go │ │ │ │ ├── parse_attr_linux.go │ │ │ │ ├── rdma_link_linux.go │ │ │ │ ├── route_linux.go │ │ │ │ ├── seg6_linux.go │ │ │ │ ├── seg6local_linux.go │ │ │ │ ├── syscall.go │ │ │ │ ├── tc_linux.go │ │ │ │ ├── xfrm_linux.go │ │ │ │ ├── xfrm_monitor_linux.go │ │ │ │ ├── xfrm_policy_linux.go │ │ │ │ └── xfrm_state_linux.go │ │ │ ├── order.go │ │ │ ├── protinfo.go │ │ │ ├── protinfo_linux.go │ │ │ ├── qdisc.go │ │ │ ├── qdisc_linux.go │ │ │ ├── rdma_link_linux.go │ │ │ ├── route.go │ │ │ ├── route_linux.go │ │ │ ├── route_unspecified.go │ │ │ ├── rule.go │ │ │ ├── rule_linux.go │ │ │ ├── socket.go │ │ │ ├── socket_linux.go │ │ │ ├── tcp.go │ │ │ ├── tcp_linux.go │ │ │ ├── xfrm.go │ │ │ ├── xfrm_monitor_linux.go │ │ │ ├── xfrm_policy.go │ │ │ ├── xfrm_policy_linux.go │ │ │ ├── xfrm_state.go │ │ │ └── xfrm_state_linux.go │ │ ├── peercred/ │ │ │ ├── AUTHORS │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── peercred.go │ │ │ ├── peercred_darwin.go │ │ │ ├── peercred_freebsd.go │ │ │ └── peercred_linux.go │ │ ├── web-client-prebuilt/ │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── build/ │ │ │ │ └── index.html │ │ │ └── embed.go │ │ └── wireguard-go/ │ │ ├── LICENSE │ │ ├── conn/ │ │ │ ├── bind_std.go │ │ │ ├── bind_windows.go │ │ │ ├── boundif_android.go │ │ │ ├── conn.go │ │ │ ├── control_default.go │ │ │ ├── control_linux.go │ │ │ ├── controlfns.go │ │ │ ├── controlfns_linux.go │ │ │ ├── controlfns_unix.go │ │ │ ├── controlfns_windows.go │ │ │ ├── default.go │ │ │ ├── erraddrinuse.go │ │ │ ├── errors_default.go │ │ │ ├── errors_linux.go │ │ │ ├── features_default.go │ │ │ ├── features_linux.go │ │ │ ├── mark_default.go │ │ │ ├── mark_unix.go │ │ │ └── winrio/ │ │ │ └── rio_windows.go │ │ ├── device/ │ │ │ ├── allowedips.go │ │ │ ├── channels.go │ │ │ ├── constants.go │ │ │ ├── cookie.go │ │ │ ├── device.go │ │ │ ├── devicestate_string.go │ │ │ ├── indextable.go │ │ │ ├── ip.go │ │ │ ├── keypair.go │ │ │ ├── logger.go │ │ │ ├── mobilequirks.go │ │ │ ├── noise-helpers.go │ │ │ ├── noise-protocol.go │ │ │ ├── noise-types.go │ │ │ ├── peer.go │ │ │ ├── pools.go │ │ │ ├── queueconstants_android.go │ │ │ ├── queueconstants_default.go │ │ │ ├── queueconstants_ios.go │ │ │ ├── queueconstants_windows.go │ │ │ ├── receive.go │ │ │ ├── send.go │ │ │ ├── sticky_default.go │ │ │ ├── sticky_linux.go │ │ │ ├── timers.go │ │ │ ├── tun.go │ │ │ └── uapi.go │ │ ├── ipc/ │ │ │ ├── namedpipe/ │ │ │ │ ├── file.go │ │ │ │ └── namedpipe.go │ │ │ ├── uapi_bsd.go │ │ │ ├── uapi_fake.go │ │ │ ├── uapi_linux.go │ │ │ ├── uapi_tamago.go │ │ │ ├── uapi_unix.go │ │ │ └── uapi_windows.go │ │ ├── ratelimiter/ │ │ │ └── ratelimiter.go │ │ ├── replay/ │ │ │ └── replay.go │ │ ├── rwcancel/ │ │ │ ├── rwcancel.go │ │ │ └── rwcancel_stub.go │ │ ├── tai64n/ │ │ │ └── tai64n.go │ │ └── tun/ │ │ ├── checksum.go │ │ ├── checksum_amd64.go │ │ ├── checksum_generated_amd64.go │ │ ├── checksum_generated_amd64.s │ │ ├── checksum_generic.go │ │ ├── errors.go │ │ ├── offload.go │ │ ├── offload_linux.go │ │ ├── operateonfd.go │ │ ├── tun.go │ │ ├── tun_darwin.go │ │ ├── tun_freebsd.go │ │ ├── tun_linux.go │ │ ├── tun_openbsd.go │ │ └── tun_windows.go │ ├── takama/ │ │ └── daemon/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── daemon.go │ │ ├── daemon_darwin.go │ │ ├── daemon_freebsd.go │ │ ├── daemon_linux.go │ │ ├── daemon_linux_systemd.go │ │ ├── daemon_linux_systemv.go │ │ ├── daemon_linux_upstart.go │ │ ├── daemon_windows.go │ │ ├── helper.go │ │ ├── helper_legacy.go │ │ └── helper_windows.go │ ├── tcnksm/ │ │ ├── go-gitconfig/ │ │ │ ├── .gitignore │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── gitconfig.go │ │ │ └── wercker.yml │ │ └── go-httpstat/ │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── go18.go │ │ ├── httpstat.go │ │ └── pre_go18.go │ ├── tidwall/ │ │ └── jsonc/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── jsonc.go │ ├── tonistiigi/ │ │ ├── go-csvvalue/ │ │ │ ├── .golangci.yml │ │ │ ├── .yamllint.yml │ │ │ ├── Dockerfile │ │ │ ├── LICENSE │ │ │ ├── codecov.yml │ │ │ ├── csvvalue.go │ │ │ ├── docker-bake.hcl │ │ │ └── readme.md │ │ ├── units/ │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── bytes.go │ │ │ └── readme.md │ │ └── vt100/ │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── command.go │ │ ├── scanner.go │ │ └── vt100.go │ ├── u-root/ │ │ └── uio/ │ │ ├── LICENSE │ │ ├── rand/ │ │ │ ├── random.go │ │ │ ├── random_linux.go │ │ │ ├── random_std.go │ │ │ ├── random_unix.go │ │ │ └── random_urandom.go │ │ └── uio/ │ │ ├── alignreader.go │ │ ├── alignwriter.go │ │ ├── archivereader.go │ │ ├── buffer.go │ │ ├── cached.go │ │ ├── lazy.go │ │ ├── linewriter.go │ │ ├── multiwriter.go │ │ ├── null.go │ │ ├── progress.go │ │ ├── reader.go │ │ └── uio.go │ ├── ulikunitz/ │ │ └── xz/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── TODO.md │ │ ├── bits.go │ │ ├── crc.go │ │ ├── format.go │ │ ├── fox-check-none.xz │ │ ├── fox.xz │ │ ├── internal/ │ │ │ ├── hash/ │ │ │ │ ├── cyclic_poly.go │ │ │ │ ├── doc.go │ │ │ │ ├── rabin_karp.go │ │ │ │ └── roller.go │ │ │ └── xlog/ │ │ │ └── xlog.go │ │ ├── lzma/ │ │ │ ├── bintree.go │ │ │ ├── bitops.go │ │ │ ├── breader.go │ │ │ ├── buffer.go │ │ │ ├── bytewriter.go │ │ │ ├── decoder.go │ │ │ ├── decoderdict.go │ │ │ ├── directcodec.go │ │ │ ├── distcodec.go │ │ │ ├── encoder.go │ │ │ ├── encoderdict.go │ │ │ ├── fox.lzma │ │ │ ├── hashtable.go │ │ │ ├── header.go │ │ │ ├── header2.go │ │ │ ├── lengthcodec.go │ │ │ ├── literalcodec.go │ │ │ ├── matchalgorithm.go │ │ │ ├── operation.go │ │ │ ├── prob.go │ │ │ ├── properties.go │ │ │ ├── rangecodec.go │ │ │ ├── reader.go │ │ │ ├── reader2.go │ │ │ ├── state.go │ │ │ ├── treecodecs.go │ │ │ ├── writer.go │ │ │ └── writer2.go │ │ ├── lzmafilter.go │ │ ├── make-docs │ │ ├── none-check.go │ │ ├── reader.go │ │ └── writer.go │ ├── vbatts/ │ │ └── tar-split/ │ │ ├── LICENSE │ │ └── archive/ │ │ └── tar/ │ │ ├── common.go │ │ ├── format.go │ │ ├── reader.go │ │ ├── stat_actime1.go │ │ ├── stat_actime2.go │ │ ├── stat_unix.go │ │ ├── strconv.go │ │ └── writer.go │ ├── vishvananda/ │ │ └── netns/ │ │ ├── .golangci.yml │ │ ├── .yamllint.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── doc.go │ │ ├── netns_linux.go │ │ ├── netns_others.go │ │ ├── nshandle_linux.go │ │ └── nshandle_others.go │ ├── x448/ │ │ └── float16/ │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── README.md │ │ └── float16.go │ └── xeipuuv/ │ ├── gojsonpointer/ │ │ ├── LICENSE-APACHE-2.0.txt │ │ ├── README.md │ │ └── pointer.go │ ├── gojsonreference/ │ │ ├── LICENSE-APACHE-2.0.txt │ │ ├── README.md │ │ └── reference.go │ └── gojsonschema/ │ ├── .gitignore │ ├── .travis.yml │ ├── LICENSE-APACHE-2.0.txt │ ├── README.md │ ├── draft.go │ ├── errors.go │ ├── format_checkers.go │ ├── glide.yaml │ ├── internalLog.go │ ├── jsonContext.go │ ├── jsonLoader.go │ ├── locales.go │ ├── result.go │ ├── schema.go │ ├── schemaLoader.go │ ├── schemaPool.go │ ├── schemaReferencePool.go │ ├── schemaType.go │ ├── subSchema.go │ ├── types.go │ ├── utils.go │ └── validation.go ├── go.etcd.io/ │ └── etcd/ │ ├── api/ │ │ └── v3/ │ │ ├── LICENSE │ │ ├── authpb/ │ │ │ ├── auth.pb.go │ │ │ └── auth.proto │ │ ├── etcdserverpb/ │ │ │ ├── etcdserver.pb.go │ │ │ ├── etcdserver.proto │ │ │ ├── raft_internal.pb.go │ │ │ ├── raft_internal.proto │ │ │ ├── raft_internal_stringer.go │ │ │ ├── rpc.pb.go │ │ │ └── rpc.proto │ │ ├── membershippb/ │ │ │ ├── membership.pb.go │ │ │ └── membership.proto │ │ ├── mvccpb/ │ │ │ ├── kv.pb.go │ │ │ └── kv.proto │ │ ├── v3rpc/ │ │ │ └── rpctypes/ │ │ │ ├── doc.go │ │ │ ├── error.go │ │ │ ├── md.go │ │ │ └── metadatafields.go │ │ └── version/ │ │ └── version.go │ └── client/ │ ├── pkg/ │ │ └── v3/ │ │ ├── LICENSE │ │ ├── fileutil/ │ │ │ ├── dir_unix.go │ │ │ ├── dir_windows.go │ │ │ ├── doc.go │ │ │ ├── filereader.go │ │ │ ├── fileutil.go │ │ │ ├── lock.go │ │ │ ├── lock_flock.go │ │ │ ├── lock_linux.go │ │ │ ├── lock_plan9.go │ │ │ ├── lock_solaris.go │ │ │ ├── lock_unix.go │ │ │ ├── lock_windows.go │ │ │ ├── preallocate.go │ │ │ ├── preallocate_darwin.go │ │ │ ├── preallocate_unix.go │ │ │ ├── preallocate_unsupported.go │ │ │ ├── purge.go │ │ │ ├── read_dir.go │ │ │ ├── sync.go │ │ │ ├── sync_darwin.go │ │ │ └── sync_linux.go │ │ ├── logutil/ │ │ │ ├── doc.go │ │ │ ├── log_level.go │ │ │ ├── zap.go │ │ │ └── zap_journal.go │ │ ├── systemd/ │ │ │ ├── doc.go │ │ │ └── journal.go │ │ ├── tlsutil/ │ │ │ ├── cipher_suites.go │ │ │ ├── doc.go │ │ │ ├── tlsutil.go │ │ │ └── versions.go │ │ ├── transport/ │ │ │ ├── doc.go │ │ │ ├── keepalive_listener.go │ │ │ ├── keepalive_listener_openbsd.go │ │ │ ├── keepalive_listener_unix.go │ │ │ ├── limit_listen.go │ │ │ ├── listener.go │ │ │ ├── listener_opts.go │ │ │ ├── listener_tls.go │ │ │ ├── sockopt.go │ │ │ ├── sockopt_solaris.go │ │ │ ├── sockopt_unix.go │ │ │ ├── sockopt_windows.go │ │ │ ├── timeout_conn.go │ │ │ ├── timeout_dialer.go │ │ │ ├── timeout_listener.go │ │ │ ├── timeout_transport.go │ │ │ ├── tls.go │ │ │ ├── transport.go │ │ │ └── unix_listener.go │ │ └── types/ │ │ ├── doc.go │ │ ├── id.go │ │ ├── set.go │ │ ├── slice.go │ │ ├── urls.go │ │ └── urlsmap.go │ └── v3/ │ ├── LICENSE │ ├── README.md │ ├── auth.go │ ├── client.go │ ├── cluster.go │ ├── compact_op.go │ ├── compare.go │ ├── config.go │ ├── credentials/ │ │ └── credentials.go │ ├── ctx.go │ ├── doc.go │ ├── internal/ │ │ ├── endpoint/ │ │ │ └── endpoint.go │ │ └── resolver/ │ │ └── resolver.go │ ├── kubernetes/ │ │ ├── client.go │ │ └── interface.go │ ├── kv.go │ ├── lease.go │ ├── logger.go │ ├── maintenance.go │ ├── op.go │ ├── options.go │ ├── retry.go │ ├── retry_interceptor.go │ ├── sort.go │ ├── txn.go │ ├── utils.go │ └── watch.go ├── go.opentelemetry.io/ │ ├── auto/ │ │ └── sdk/ │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── VERSIONING.md │ │ ├── doc.go │ │ ├── internal/ │ │ │ └── telemetry/ │ │ │ ├── attr.go │ │ │ ├── doc.go │ │ │ ├── id.go │ │ │ ├── number.go │ │ │ ├── resource.go │ │ │ ├── scope.go │ │ │ ├── span.go │ │ │ ├── status.go │ │ │ ├── traces.go │ │ │ └── value.go │ │ ├── limit.go │ │ ├── span.go │ │ ├── tracer.go │ │ └── tracer_provider.go │ ├── contrib/ │ │ └── instrumentation/ │ │ ├── google.golang.org/ │ │ │ └── grpc/ │ │ │ └── otelgrpc/ │ │ │ ├── LICENSE │ │ │ ├── config.go │ │ │ ├── doc.go │ │ │ ├── interceptor.go │ │ │ ├── interceptorinfo.go │ │ │ ├── internal/ │ │ │ │ └── parse.go │ │ │ ├── metadata_supplier.go │ │ │ ├── semconv.go │ │ │ ├── stats_handler.go │ │ │ └── version.go │ │ └── net/ │ │ └── http/ │ │ ├── httptrace/ │ │ │ └── otelhttptrace/ │ │ │ ├── LICENSE │ │ │ ├── api.go │ │ │ ├── clienttrace.go │ │ │ ├── httptrace.go │ │ │ ├── internal/ │ │ │ │ └── semconvutil/ │ │ │ │ ├── gen.go │ │ │ │ ├── httpconv.go │ │ │ │ └── netconv.go │ │ │ └── version.go │ │ └── otelhttp/ │ │ ├── LICENSE │ │ ├── client.go │ │ ├── common.go │ │ ├── config.go │ │ ├── doc.go │ │ ├── handler.go │ │ ├── internal/ │ │ │ ├── request/ │ │ │ │ ├── body_wrapper.go │ │ │ │ └── resp_writer_wrapper.go │ │ │ ├── semconv/ │ │ │ │ ├── env.go │ │ │ │ ├── httpconv.go │ │ │ │ ├── util.go │ │ │ │ └── v1.20.0.go │ │ │ └── semconvutil/ │ │ │ ├── gen.go │ │ │ ├── httpconv.go │ │ │ └── netconv.go │ │ ├── labeler.go │ │ ├── start_time_context.go │ │ ├── transport.go │ │ └── version.go │ ├── otel/ │ │ ├── .codespellignore │ │ ├── .codespellrc │ │ ├── .gitattributes │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── .lycheeignore │ │ ├── .markdownlint.yaml │ │ ├── CHANGELOG.md │ │ ├── CODEOWNERS │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── README.md │ │ ├── RELEASING.md │ │ ├── VERSIONING.md │ │ ├── attribute/ │ │ │ ├── README.md │ │ │ ├── doc.go │ │ │ ├── encoder.go │ │ │ ├── filter.go │ │ │ ├── iterator.go │ │ │ ├── key.go │ │ │ ├── kv.go │ │ │ ├── set.go │ │ │ ├── type_string.go │ │ │ └── value.go │ │ ├── baggage/ │ │ │ ├── README.md │ │ │ ├── baggage.go │ │ │ ├── context.go │ │ │ └── doc.go │ │ ├── codes/ │ │ │ ├── README.md │ │ │ ├── codes.go │ │ │ └── doc.go │ │ ├── doc.go │ │ ├── error_handler.go │ │ ├── exporters/ │ │ │ └── otlp/ │ │ │ └── otlptrace/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── clients.go │ │ │ ├── doc.go │ │ │ ├── exporter.go │ │ │ ├── internal/ │ │ │ │ └── tracetransform/ │ │ │ │ ├── attribute.go │ │ │ │ ├── instrumentation.go │ │ │ │ ├── resource.go │ │ │ │ └── span.go │ │ │ ├── otlptracegrpc/ │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── client.go │ │ │ │ ├── doc.go │ │ │ │ ├── exporter.go │ │ │ │ ├── internal/ │ │ │ │ │ ├── envconfig/ │ │ │ │ │ │ └── envconfig.go │ │ │ │ │ ├── gen.go │ │ │ │ │ ├── otlpconfig/ │ │ │ │ │ │ ├── envconfig.go │ │ │ │ │ │ ├── options.go │ │ │ │ │ │ ├── optiontypes.go │ │ │ │ │ │ └── tls.go │ │ │ │ │ ├── partialsuccess.go │ │ │ │ │ └── retry/ │ │ │ │ │ └── retry.go │ │ │ │ └── options.go │ │ │ └── version.go │ │ ├── get_main_pkgs.sh │ │ ├── handler.go │ │ ├── internal/ │ │ │ ├── attribute/ │ │ │ │ └── attribute.go │ │ │ ├── baggage/ │ │ │ │ ├── baggage.go │ │ │ │ └── context.go │ │ │ ├── gen.go │ │ │ ├── global/ │ │ │ │ ├── handler.go │ │ │ │ ├── instruments.go │ │ │ │ ├── internal_logging.go │ │ │ │ ├── meter.go │ │ │ │ ├── propagator.go │ │ │ │ ├── state.go │ │ │ │ └── trace.go │ │ │ └── rawhelpers.go │ │ ├── internal_logging.go │ │ ├── metric/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── asyncfloat64.go │ │ │ ├── asyncint64.go │ │ │ ├── config.go │ │ │ ├── doc.go │ │ │ ├── embedded/ │ │ │ │ ├── README.md │ │ │ │ └── embedded.go │ │ │ ├── instrument.go │ │ │ ├── meter.go │ │ │ ├── noop/ │ │ │ │ ├── README.md │ │ │ │ └── noop.go │ │ │ ├── syncfloat64.go │ │ │ └── syncint64.go │ │ ├── metric.go │ │ ├── propagation/ │ │ │ ├── README.md │ │ │ ├── baggage.go │ │ │ ├── doc.go │ │ │ ├── propagation.go │ │ │ └── trace_context.go │ │ ├── propagation.go │ │ ├── renovate.json │ │ ├── requirements.txt │ │ ├── sdk/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── instrumentation/ │ │ │ │ ├── README.md │ │ │ │ ├── doc.go │ │ │ │ ├── library.go │ │ │ │ └── scope.go │ │ │ ├── internal/ │ │ │ │ ├── env/ │ │ │ │ │ └── env.go │ │ │ │ └── x/ │ │ │ │ ├── README.md │ │ │ │ └── x.go │ │ │ ├── resource/ │ │ │ │ ├── README.md │ │ │ │ ├── auto.go │ │ │ │ ├── builtin.go │ │ │ │ ├── config.go │ │ │ │ ├── container.go │ │ │ │ ├── doc.go │ │ │ │ ├── env.go │ │ │ │ ├── host_id.go │ │ │ │ ├── host_id_bsd.go │ │ │ │ ├── host_id_darwin.go │ │ │ │ ├── host_id_exec.go │ │ │ │ ├── host_id_linux.go │ │ │ │ ├── host_id_readfile.go │ │ │ │ ├── host_id_unsupported.go │ │ │ │ ├── host_id_windows.go │ │ │ │ ├── os.go │ │ │ │ ├── os_release_darwin.go │ │ │ │ ├── os_release_unix.go │ │ │ │ ├── os_unix.go │ │ │ │ ├── os_unsupported.go │ │ │ │ ├── os_windows.go │ │ │ │ ├── process.go │ │ │ │ └── resource.go │ │ │ ├── trace/ │ │ │ │ ├── README.md │ │ │ │ ├── batch_span_processor.go │ │ │ │ ├── doc.go │ │ │ │ ├── event.go │ │ │ │ ├── evictedqueue.go │ │ │ │ ├── id_generator.go │ │ │ │ ├── link.go │ │ │ │ ├── provider.go │ │ │ │ ├── sampler_env.go │ │ │ │ ├── sampling.go │ │ │ │ ├── simple_span_processor.go │ │ │ │ ├── snapshot.go │ │ │ │ ├── span.go │ │ │ │ ├── span_exporter.go │ │ │ │ ├── span_limits.go │ │ │ │ ├── span_processor.go │ │ │ │ ├── tracer.go │ │ │ │ └── version.go │ │ │ └── version.go │ │ ├── semconv/ │ │ │ ├── v1.17.0/ │ │ │ │ ├── README.md │ │ │ │ ├── doc.go │ │ │ │ ├── event.go │ │ │ │ ├── exception.go │ │ │ │ ├── http.go │ │ │ │ ├── resource.go │ │ │ │ ├── schema.go │ │ │ │ └── trace.go │ │ │ ├── v1.20.0/ │ │ │ │ ├── README.md │ │ │ │ ├── attribute_group.go │ │ │ │ ├── doc.go │ │ │ │ ├── event.go │ │ │ │ ├── exception.go │ │ │ │ ├── http.go │ │ │ │ ├── resource.go │ │ │ │ ├── schema.go │ │ │ │ └── trace.go │ │ │ ├── v1.21.0/ │ │ │ │ ├── README.md │ │ │ │ ├── attribute_group.go │ │ │ │ ├── doc.go │ │ │ │ ├── event.go │ │ │ │ ├── exception.go │ │ │ │ ├── resource.go │ │ │ │ ├── schema.go │ │ │ │ └── trace.go │ │ │ └── v1.26.0/ │ │ │ ├── README.md │ │ │ ├── attribute_group.go │ │ │ ├── doc.go │ │ │ ├── exception.go │ │ │ ├── metric.go │ │ │ └── schema.go │ │ ├── trace/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── config.go │ │ │ ├── context.go │ │ │ ├── doc.go │ │ │ ├── embedded/ │ │ │ │ ├── README.md │ │ │ │ └── embedded.go │ │ │ ├── nonrecording.go │ │ │ ├── noop/ │ │ │ │ ├── README.md │ │ │ │ └── noop.go │ │ │ ├── noop.go │ │ │ ├── provider.go │ │ │ ├── span.go │ │ │ ├── trace.go │ │ │ ├── tracer.go │ │ │ └── tracestate.go │ │ ├── trace.go │ │ ├── verify_readmes.sh │ │ ├── verify_released_changelog.sh │ │ ├── version.go │ │ └── versions.yaml │ └── proto/ │ └── otlp/ │ ├── LICENSE │ ├── collector/ │ │ └── trace/ │ │ └── v1/ │ │ ├── trace_service.pb.go │ │ ├── trace_service.pb.gw.go │ │ └── trace_service_grpc.pb.go │ ├── common/ │ │ └── v1/ │ │ └── common.pb.go │ ├── resource/ │ │ └── v1/ │ │ └── resource.pb.go │ └── trace/ │ └── v1/ │ └── trace.pb.go ├── go4.org/ │ ├── mem/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── fields.go │ │ ├── fold.go │ │ └── mem.go │ └── netipx/ │ ├── .gitignore │ ├── .gitmodules │ ├── AUTHORS │ ├── LICENSE │ ├── README.md │ ├── ipset.go │ ├── mask6.go │ ├── netipx.go │ └── uint128.go ├── golang.org/ │ └── x/ │ ├── crypto/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── argon2/ │ │ │ ├── argon2.go │ │ │ ├── blake2b.go │ │ │ ├── blamka_amd64.go │ │ │ ├── blamka_amd64.s │ │ │ ├── blamka_generic.go │ │ │ └── blamka_ref.go │ │ ├── blake2b/ │ │ │ ├── blake2b.go │ │ │ ├── blake2bAVX2_amd64.go │ │ │ ├── blake2bAVX2_amd64.s │ │ │ ├── blake2b_amd64.s │ │ │ ├── blake2b_generic.go │ │ │ ├── blake2b_ref.go │ │ │ ├── blake2x.go │ │ │ └── register.go │ │ ├── blake2s/ │ │ │ ├── blake2s.go │ │ │ ├── blake2s_386.go │ │ │ ├── blake2s_386.s │ │ │ ├── blake2s_amd64.go │ │ │ ├── blake2s_amd64.s │ │ │ ├── blake2s_generic.go │ │ │ ├── blake2s_ref.go │ │ │ └── blake2x.go │ │ ├── blowfish/ │ │ │ ├── block.go │ │ │ ├── cipher.go │ │ │ └── const.go │ │ ├── cast5/ │ │ │ └── cast5.go │ │ ├── chacha20/ │ │ │ ├── chacha_arm64.go │ │ │ ├── chacha_arm64.s │ │ │ ├── chacha_generic.go │ │ │ ├── chacha_noasm.go │ │ │ ├── chacha_ppc64x.go │ │ │ ├── chacha_ppc64x.s │ │ │ ├── chacha_s390x.go │ │ │ ├── chacha_s390x.s │ │ │ └── xor.go │ │ ├── chacha20poly1305/ │ │ │ ├── chacha20poly1305.go │ │ │ ├── chacha20poly1305_amd64.go │ │ │ ├── chacha20poly1305_amd64.s │ │ │ ├── chacha20poly1305_generic.go │ │ │ ├── chacha20poly1305_noasm.go │ │ │ └── xchacha20poly1305.go │ │ ├── cryptobyte/ │ │ │ ├── asn1/ │ │ │ │ └── asn1.go │ │ │ ├── asn1.go │ │ │ ├── builder.go │ │ │ └── string.go │ │ ├── curve25519/ │ │ │ └── curve25519.go │ │ ├── ed25519/ │ │ │ └── ed25519.go │ │ ├── hkdf/ │ │ │ └── hkdf.go │ │ ├── internal/ │ │ │ ├── alias/ │ │ │ │ ├── alias.go │ │ │ │ └── alias_purego.go │ │ │ └── poly1305/ │ │ │ ├── mac_noasm.go │ │ │ ├── poly1305.go │ │ │ ├── sum_amd64.go │ │ │ ├── sum_amd64.s │ │ │ ├── sum_generic.go │ │ │ ├── sum_ppc64x.go │ │ │ ├── sum_ppc64x.s │ │ │ ├── sum_s390x.go │ │ │ └── sum_s390x.s │ │ ├── nacl/ │ │ │ ├── box/ │ │ │ │ └── box.go │ │ │ ├── secretbox/ │ │ │ │ └── secretbox.go │ │ │ └── sign/ │ │ │ └── sign.go │ │ ├── openpgp/ │ │ │ ├── armor/ │ │ │ │ ├── armor.go │ │ │ │ └── encode.go │ │ │ ├── canonical_text.go │ │ │ ├── elgamal/ │ │ │ │ └── elgamal.go │ │ │ ├── errors/ │ │ │ │ └── errors.go │ │ │ ├── keys.go │ │ │ ├── packet/ │ │ │ │ ├── compressed.go │ │ │ │ ├── config.go │ │ │ │ ├── encrypted_key.go │ │ │ │ ├── literal.go │ │ │ │ ├── ocfb.go │ │ │ │ ├── one_pass_signature.go │ │ │ │ ├── opaque.go │ │ │ │ ├── packet.go │ │ │ │ ├── private_key.go │ │ │ │ ├── public_key.go │ │ │ │ ├── public_key_v3.go │ │ │ │ ├── reader.go │ │ │ │ ├── signature.go │ │ │ │ ├── signature_v3.go │ │ │ │ ├── symmetric_key_encrypted.go │ │ │ │ ├── symmetrically_encrypted.go │ │ │ │ ├── userattribute.go │ │ │ │ └── userid.go │ │ │ ├── read.go │ │ │ ├── s2k/ │ │ │ │ └── s2k.go │ │ │ └── write.go │ │ ├── pbkdf2/ │ │ │ └── pbkdf2.go │ │ ├── pkcs12/ │ │ │ ├── bmp-string.go │ │ │ ├── crypto.go │ │ │ ├── errors.go │ │ │ ├── internal/ │ │ │ │ └── rc2/ │ │ │ │ └── rc2.go │ │ │ ├── mac.go │ │ │ ├── pbkdf.go │ │ │ ├── pkcs12.go │ │ │ └── safebags.go │ │ ├── poly1305/ │ │ │ └── poly1305_compat.go │ │ ├── salsa20/ │ │ │ └── salsa/ │ │ │ ├── hsalsa20.go │ │ │ ├── salsa208.go │ │ │ ├── salsa20_amd64.go │ │ │ ├── salsa20_amd64.s │ │ │ ├── salsa20_noasm.go │ │ │ └── salsa20_ref.go │ │ └── ssh/ │ │ ├── agent/ │ │ │ ├── client.go │ │ │ ├── forward.go │ │ │ ├── keyring.go │ │ │ └── server.go │ │ ├── buffer.go │ │ ├── certs.go │ │ ├── channel.go │ │ ├── cipher.go │ │ ├── client.go │ │ ├── client_auth.go │ │ ├── common.go │ │ ├── connection.go │ │ ├── doc.go │ │ ├── handshake.go │ │ ├── internal/ │ │ │ └── bcrypt_pbkdf/ │ │ │ └── bcrypt_pbkdf.go │ │ ├── kex.go │ │ ├── keys.go │ │ ├── mac.go │ │ ├── messages.go │ │ ├── mux.go │ │ ├── server.go │ │ ├── session.go │ │ ├── ssh_gss.go │ │ ├── streamlocal.go │ │ ├── tcpip.go │ │ └── transport.go │ ├── exp/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── constraints/ │ │ │ └── constraints.go │ │ ├── maps/ │ │ │ └── maps.go │ │ └── slices/ │ │ ├── slices.go │ │ └── sort.go │ ├── mod/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ └── semver/ │ │ └── semver.go │ ├── net/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── bpf/ │ │ │ ├── asm.go │ │ │ ├── constants.go │ │ │ ├── doc.go │ │ │ ├── instructions.go │ │ │ ├── setter.go │ │ │ ├── vm.go │ │ │ └── vm_instructions.go │ │ ├── context/ │ │ │ └── context.go │ │ ├── dns/ │ │ │ └── dnsmessage/ │ │ │ └── message.go │ │ ├── html/ │ │ │ ├── atom/ │ │ │ │ ├── atom.go │ │ │ │ └── table.go │ │ │ ├── charset/ │ │ │ │ └── charset.go │ │ │ ├── const.go │ │ │ ├── doc.go │ │ │ ├── doctype.go │ │ │ ├── entity.go │ │ │ ├── escape.go │ │ │ ├── foreign.go │ │ │ ├── iter.go │ │ │ ├── node.go │ │ │ ├── parse.go │ │ │ ├── render.go │ │ │ └── token.go │ │ ├── http/ │ │ │ ├── httpguts/ │ │ │ │ ├── guts.go │ │ │ │ └── httplex.go │ │ │ └── httpproxy/ │ │ │ └── proxy.go │ │ ├── http2/ │ │ │ ├── .gitignore │ │ │ ├── ascii.go │ │ │ ├── ciphers.go │ │ │ ├── client_conn_pool.go │ │ │ ├── config.go │ │ │ ├── config_go124.go │ │ │ ├── config_pre_go124.go │ │ │ ├── databuffer.go │ │ │ ├── errors.go │ │ │ ├── flow.go │ │ │ ├── frame.go │ │ │ ├── gotrack.go │ │ │ ├── h2c/ │ │ │ │ └── h2c.go │ │ │ ├── hpack/ │ │ │ │ ├── encode.go │ │ │ │ ├── hpack.go │ │ │ │ ├── huffman.go │ │ │ │ ├── static_table.go │ │ │ │ └── tables.go │ │ │ ├── http2.go │ │ │ ├── pipe.go │ │ │ ├── server.go │ │ │ ├── timer.go │ │ │ ├── transport.go │ │ │ ├── unencrypted.go │ │ │ ├── write.go │ │ │ ├── writesched.go │ │ │ ├── writesched_priority.go │ │ │ ├── writesched_random.go │ │ │ └── writesched_roundrobin.go │ │ ├── icmp/ │ │ │ ├── dstunreach.go │ │ │ ├── echo.go │ │ │ ├── endpoint.go │ │ │ ├── extension.go │ │ │ ├── helper_posix.go │ │ │ ├── interface.go │ │ │ ├── ipv4.go │ │ │ ├── ipv6.go │ │ │ ├── listen_posix.go │ │ │ ├── listen_stub.go │ │ │ ├── message.go │ │ │ ├── messagebody.go │ │ │ ├── mpls.go │ │ │ ├── multipart.go │ │ │ ├── packettoobig.go │ │ │ ├── paramprob.go │ │ │ ├── sys_freebsd.go │ │ │ └── timeexceeded.go │ │ ├── idna/ │ │ │ ├── go118.go │ │ │ ├── idna10.0.0.go │ │ │ ├── idna9.0.0.go │ │ │ ├── pre_go118.go │ │ │ ├── punycode.go │ │ │ ├── tables10.0.0.go │ │ │ ├── tables11.0.0.go │ │ │ ├── tables12.0.0.go │ │ │ ├── tables13.0.0.go │ │ │ ├── tables15.0.0.go │ │ │ ├── tables9.0.0.go │ │ │ ├── trie.go │ │ │ ├── trie12.0.0.go │ │ │ ├── trie13.0.0.go │ │ │ └── trieval.go │ │ ├── internal/ │ │ │ ├── httpcommon/ │ │ │ │ ├── ascii.go │ │ │ │ ├── headermap.go │ │ │ │ └── request.go │ │ │ ├── iana/ │ │ │ │ └── const.go │ │ │ ├── socket/ │ │ │ │ ├── cmsghdr.go │ │ │ │ ├── cmsghdr_bsd.go │ │ │ │ ├── cmsghdr_linux_32bit.go │ │ │ │ ├── cmsghdr_linux_64bit.go │ │ │ │ ├── cmsghdr_solaris_64bit.go │ │ │ │ ├── cmsghdr_stub.go │ │ │ │ ├── cmsghdr_unix.go │ │ │ │ ├── cmsghdr_zos_s390x.go │ │ │ │ ├── complete_dontwait.go │ │ │ │ ├── complete_nodontwait.go │ │ │ │ ├── empty.s │ │ │ │ ├── error_unix.go │ │ │ │ ├── error_windows.go │ │ │ │ ├── iovec_32bit.go │ │ │ │ ├── iovec_64bit.go │ │ │ │ ├── iovec_solaris_64bit.go │ │ │ │ ├── iovec_stub.go │ │ │ │ ├── mmsghdr_stub.go │ │ │ │ ├── mmsghdr_unix.go │ │ │ │ ├── msghdr_bsd.go │ │ │ │ ├── msghdr_bsdvar.go │ │ │ │ ├── msghdr_linux.go │ │ │ │ ├── msghdr_linux_32bit.go │ │ │ │ ├── msghdr_linux_64bit.go │ │ │ │ ├── msghdr_openbsd.go │ │ │ │ ├── msghdr_solaris_64bit.go │ │ │ │ ├── msghdr_stub.go │ │ │ │ ├── msghdr_zos_s390x.go │ │ │ │ ├── norace.go │ │ │ │ ├── race.go │ │ │ │ ├── rawconn.go │ │ │ │ ├── rawconn_mmsg.go │ │ │ │ ├── rawconn_msg.go │ │ │ │ ├── rawconn_nommsg.go │ │ │ │ ├── rawconn_nomsg.go │ │ │ │ ├── socket.go │ │ │ │ ├── sys.go │ │ │ │ ├── sys_bsd.go │ │ │ │ ├── sys_const_unix.go │ │ │ │ ├── sys_linux.go │ │ │ │ ├── sys_linux_386.go │ │ │ │ ├── sys_linux_386.s │ │ │ │ ├── sys_linux_amd64.go │ │ │ │ ├── sys_linux_arm.go │ │ │ │ ├── sys_linux_arm64.go │ │ │ │ ├── sys_linux_loong64.go │ │ │ │ ├── sys_linux_mips.go │ │ │ │ ├── sys_linux_mips64.go │ │ │ │ ├── sys_linux_mips64le.go │ │ │ │ ├── sys_linux_mipsle.go │ │ │ │ ├── sys_linux_ppc.go │ │ │ │ ├── sys_linux_ppc64.go │ │ │ │ ├── sys_linux_ppc64le.go │ │ │ │ ├── sys_linux_riscv64.go │ │ │ │ ├── sys_linux_s390x.go │ │ │ │ ├── sys_linux_s390x.s │ │ │ │ ├── sys_netbsd.go │ │ │ │ ├── sys_posix.go │ │ │ │ ├── sys_stub.go │ │ │ │ ├── sys_unix.go │ │ │ │ ├── sys_windows.go │ │ │ │ ├── sys_zos_s390x.go │ │ │ │ ├── sys_zos_s390x.s │ │ │ │ ├── zsys_aix_ppc64.go │ │ │ │ ├── zsys_darwin_amd64.go │ │ │ │ ├── zsys_darwin_arm64.go │ │ │ │ ├── zsys_dragonfly_amd64.go │ │ │ │ ├── zsys_freebsd_386.go │ │ │ │ ├── zsys_freebsd_amd64.go │ │ │ │ ├── zsys_freebsd_arm.go │ │ │ │ ├── zsys_freebsd_arm64.go │ │ │ │ ├── zsys_freebsd_riscv64.go │ │ │ │ ├── zsys_linux_386.go │ │ │ │ ├── zsys_linux_amd64.go │ │ │ │ ├── zsys_linux_arm.go │ │ │ │ ├── zsys_linux_arm64.go │ │ │ │ ├── zsys_linux_loong64.go │ │ │ │ ├── zsys_linux_mips.go │ │ │ │ ├── zsys_linux_mips64.go │ │ │ │ ├── zsys_linux_mips64le.go │ │ │ │ ├── zsys_linux_mipsle.go │ │ │ │ ├── zsys_linux_ppc.go │ │ │ │ ├── zsys_linux_ppc64.go │ │ │ │ ├── zsys_linux_ppc64le.go │ │ │ │ ├── zsys_linux_riscv64.go │ │ │ │ ├── zsys_linux_s390x.go │ │ │ │ ├── zsys_netbsd_386.go │ │ │ │ ├── zsys_netbsd_amd64.go │ │ │ │ ├── zsys_netbsd_arm.go │ │ │ │ ├── zsys_netbsd_arm64.go │ │ │ │ ├── zsys_openbsd_386.go │ │ │ │ ├── zsys_openbsd_amd64.go │ │ │ │ ├── zsys_openbsd_arm.go │ │ │ │ ├── zsys_openbsd_arm64.go │ │ │ │ ├── zsys_openbsd_mips64.go │ │ │ │ ├── zsys_openbsd_ppc64.go │ │ │ │ ├── zsys_openbsd_riscv64.go │ │ │ │ ├── zsys_solaris_amd64.go │ │ │ │ └── zsys_zos_s390x.go │ │ │ ├── socks/ │ │ │ │ ├── client.go │ │ │ │ └── socks.go │ │ │ └── timeseries/ │ │ │ └── timeseries.go │ │ ├── ipv4/ │ │ │ ├── batch.go │ │ │ ├── control.go │ │ │ ├── control_bsd.go │ │ │ ├── control_pktinfo.go │ │ │ ├── control_stub.go │ │ │ ├── control_unix.go │ │ │ ├── control_windows.go │ │ │ ├── control_zos.go │ │ │ ├── dgramopt.go │ │ │ ├── doc.go │ │ │ ├── endpoint.go │ │ │ ├── genericopt.go │ │ │ ├── header.go │ │ │ ├── helper.go │ │ │ ├── iana.go │ │ │ ├── icmp.go │ │ │ ├── icmp_linux.go │ │ │ ├── icmp_stub.go │ │ │ ├── packet.go │ │ │ ├── payload.go │ │ │ ├── payload_cmsg.go │ │ │ ├── payload_nocmsg.go │ │ │ ├── sockopt.go │ │ │ ├── sockopt_posix.go │ │ │ ├── sockopt_stub.go │ │ │ ├── sys_aix.go │ │ │ ├── sys_asmreq.go │ │ │ ├── sys_asmreq_stub.go │ │ │ ├── sys_asmreqn.go │ │ │ ├── sys_asmreqn_stub.go │ │ │ ├── sys_bpf.go │ │ │ ├── sys_bpf_stub.go │ │ │ ├── sys_bsd.go │ │ │ ├── sys_darwin.go │ │ │ ├── sys_dragonfly.go │ │ │ ├── sys_freebsd.go │ │ │ ├── sys_linux.go │ │ │ ├── sys_solaris.go │ │ │ ├── sys_ssmreq.go │ │ │ ├── sys_ssmreq_stub.go │ │ │ ├── sys_stub.go │ │ │ ├── sys_windows.go │ │ │ ├── sys_zos.go │ │ │ ├── zsys_aix_ppc64.go │ │ │ ├── zsys_darwin.go │ │ │ ├── zsys_dragonfly.go │ │ │ ├── zsys_freebsd_386.go │ │ │ ├── zsys_freebsd_amd64.go │ │ │ ├── zsys_freebsd_arm.go │ │ │ ├── zsys_freebsd_arm64.go │ │ │ ├── zsys_freebsd_riscv64.go │ │ │ ├── zsys_linux_386.go │ │ │ ├── zsys_linux_amd64.go │ │ │ ├── zsys_linux_arm.go │ │ │ ├── zsys_linux_arm64.go │ │ │ ├── zsys_linux_loong64.go │ │ │ ├── zsys_linux_mips.go │ │ │ ├── zsys_linux_mips64.go │ │ │ ├── zsys_linux_mips64le.go │ │ │ ├── zsys_linux_mipsle.go │ │ │ ├── zsys_linux_ppc.go │ │ │ ├── zsys_linux_ppc64.go │ │ │ ├── zsys_linux_ppc64le.go │ │ │ ├── zsys_linux_riscv64.go │ │ │ ├── zsys_linux_s390x.go │ │ │ ├── zsys_netbsd.go │ │ │ ├── zsys_openbsd.go │ │ │ ├── zsys_solaris.go │ │ │ └── zsys_zos_s390x.go │ │ ├── ipv6/ │ │ │ ├── batch.go │ │ │ ├── control.go │ │ │ ├── control_rfc2292_unix.go │ │ │ ├── control_rfc3542_unix.go │ │ │ ├── control_stub.go │ │ │ ├── control_unix.go │ │ │ ├── control_windows.go │ │ │ ├── dgramopt.go │ │ │ ├── doc.go │ │ │ ├── endpoint.go │ │ │ ├── genericopt.go │ │ │ ├── header.go │ │ │ ├── helper.go │ │ │ ├── iana.go │ │ │ ├── icmp.go │ │ │ ├── icmp_bsd.go │ │ │ ├── icmp_linux.go │ │ │ ├── icmp_solaris.go │ │ │ ├── icmp_stub.go │ │ │ ├── icmp_windows.go │ │ │ ├── icmp_zos.go │ │ │ ├── payload.go │ │ │ ├── payload_cmsg.go │ │ │ ├── payload_nocmsg.go │ │ │ ├── sockopt.go │ │ │ ├── sockopt_posix.go │ │ │ ├── sockopt_stub.go │ │ │ ├── sys_aix.go │ │ │ ├── sys_asmreq.go │ │ │ ├── sys_asmreq_stub.go │ │ │ ├── sys_bpf.go │ │ │ ├── sys_bpf_stub.go │ │ │ ├── sys_bsd.go │ │ │ ├── sys_darwin.go │ │ │ ├── sys_freebsd.go │ │ │ ├── sys_linux.go │ │ │ ├── sys_solaris.go │ │ │ ├── sys_ssmreq.go │ │ │ ├── sys_ssmreq_stub.go │ │ │ ├── sys_stub.go │ │ │ ├── sys_windows.go │ │ │ ├── sys_zos.go │ │ │ ├── zsys_aix_ppc64.go │ │ │ ├── zsys_darwin.go │ │ │ ├── zsys_dragonfly.go │ │ │ ├── zsys_freebsd_386.go │ │ │ ├── zsys_freebsd_amd64.go │ │ │ ├── zsys_freebsd_arm.go │ │ │ ├── zsys_freebsd_arm64.go │ │ │ ├── zsys_freebsd_riscv64.go │ │ │ ├── zsys_linux_386.go │ │ │ ├── zsys_linux_amd64.go │ │ │ ├── zsys_linux_arm.go │ │ │ ├── zsys_linux_arm64.go │ │ │ ├── zsys_linux_loong64.go │ │ │ ├── zsys_linux_mips.go │ │ │ ├── zsys_linux_mips64.go │ │ │ ├── zsys_linux_mips64le.go │ │ │ ├── zsys_linux_mipsle.go │ │ │ ├── zsys_linux_ppc.go │ │ │ ├── zsys_linux_ppc64.go │ │ │ ├── zsys_linux_ppc64le.go │ │ │ ├── zsys_linux_riscv64.go │ │ │ ├── zsys_linux_s390x.go │ │ │ ├── zsys_netbsd.go │ │ │ ├── zsys_openbsd.go │ │ │ ├── zsys_solaris.go │ │ │ └── zsys_zos_s390x.go │ │ ├── proxy/ │ │ │ ├── dial.go │ │ │ ├── direct.go │ │ │ ├── per_host.go │ │ │ ├── proxy.go │ │ │ └── socks5.go │ │ ├── route/ │ │ │ ├── address.go │ │ │ ├── binary.go │ │ │ ├── empty.s │ │ │ ├── interface.go │ │ │ ├── interface_announce.go │ │ │ ├── interface_classic.go │ │ │ ├── interface_freebsd.go │ │ │ ├── interface_multicast.go │ │ │ ├── interface_openbsd.go │ │ │ ├── message.go │ │ │ ├── route.go │ │ │ ├── route_classic.go │ │ │ ├── route_openbsd.go │ │ │ ├── sys.go │ │ │ ├── sys_darwin.go │ │ │ ├── sys_dragonfly.go │ │ │ ├── sys_freebsd.go │ │ │ ├── sys_netbsd.go │ │ │ ├── sys_openbsd.go │ │ │ ├── syscall.go │ │ │ ├── zsys_darwin.go │ │ │ ├── zsys_dragonfly.go │ │ │ ├── zsys_freebsd_386.go │ │ │ ├── zsys_freebsd_amd64.go │ │ │ ├── zsys_freebsd_arm.go │ │ │ ├── zsys_freebsd_arm64.go │ │ │ ├── zsys_freebsd_riscv64.go │ │ │ ├── zsys_netbsd.go │ │ │ └── zsys_openbsd.go │ │ ├── trace/ │ │ │ ├── events.go │ │ │ ├── histogram.go │ │ │ └── trace.go │ │ └── websocket/ │ │ ├── client.go │ │ ├── dial.go │ │ ├── hybi.go │ │ ├── server.go │ │ └── websocket.go │ ├── oauth2/ │ │ ├── .travis.yml │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── authhandler/ │ │ │ └── authhandler.go │ │ ├── deviceauth.go │ │ ├── google/ │ │ │ ├── appengine.go │ │ │ ├── default.go │ │ │ ├── doc.go │ │ │ ├── error.go │ │ │ ├── externalaccount/ │ │ │ │ ├── aws.go │ │ │ │ ├── basecredentials.go │ │ │ │ ├── executablecredsource.go │ │ │ │ ├── filecredsource.go │ │ │ │ ├── header.go │ │ │ │ ├── programmaticrefreshcredsource.go │ │ │ │ └── urlcredsource.go │ │ │ ├── google.go │ │ │ ├── internal/ │ │ │ │ ├── externalaccountauthorizeduser/ │ │ │ │ │ └── externalaccountauthorizeduser.go │ │ │ │ ├── impersonate/ │ │ │ │ │ └── impersonate.go │ │ │ │ └── stsexchange/ │ │ │ │ ├── clientauth.go │ │ │ │ └── sts_exchange.go │ │ │ ├── jwt.go │ │ │ └── sdk.go │ │ ├── internal/ │ │ │ ├── doc.go │ │ │ ├── oauth2.go │ │ │ ├── token.go │ │ │ └── transport.go │ │ ├── jws/ │ │ │ └── jws.go │ │ ├── jwt/ │ │ │ └── jwt.go │ │ ├── oauth2.go │ │ ├── pkce.go │ │ ├── token.go │ │ └── transport.go │ ├── sync/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── errgroup/ │ │ │ └── errgroup.go │ │ ├── semaphore/ │ │ │ └── semaphore.go │ │ └── singleflight/ │ │ └── singleflight.go │ ├── sys/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── cpu/ │ │ │ ├── asm_aix_ppc64.s │ │ │ ├── asm_darwin_x86_gc.s │ │ │ ├── byteorder.go │ │ │ ├── cpu.go │ │ │ ├── cpu_aix.go │ │ │ ├── cpu_arm.go │ │ │ ├── cpu_arm64.go │ │ │ ├── cpu_arm64.s │ │ │ ├── cpu_darwin_x86.go │ │ │ ├── cpu_gc_arm64.go │ │ │ ├── cpu_gc_s390x.go │ │ │ ├── cpu_gc_x86.go │ │ │ ├── cpu_gc_x86.s │ │ │ ├── cpu_gccgo_arm64.go │ │ │ ├── cpu_gccgo_s390x.go │ │ │ ├── cpu_gccgo_x86.c │ │ │ ├── cpu_gccgo_x86.go │ │ │ ├── cpu_linux.go │ │ │ ├── cpu_linux_arm.go │ │ │ ├── cpu_linux_arm64.go │ │ │ ├── cpu_linux_mips64x.go │ │ │ ├── cpu_linux_noinit.go │ │ │ ├── cpu_linux_ppc64x.go │ │ │ ├── cpu_linux_riscv64.go │ │ │ ├── cpu_linux_s390x.go │ │ │ ├── cpu_loong64.go │ │ │ ├── cpu_mips64x.go │ │ │ ├── cpu_mipsx.go │ │ │ ├── cpu_netbsd_arm64.go │ │ │ ├── cpu_openbsd_arm64.go │ │ │ ├── cpu_openbsd_arm64.s │ │ │ ├── cpu_other_arm.go │ │ │ ├── cpu_other_arm64.go │ │ │ ├── cpu_other_mips64x.go │ │ │ ├── cpu_other_ppc64x.go │ │ │ ├── cpu_other_riscv64.go │ │ │ ├── cpu_other_x86.go │ │ │ ├── cpu_ppc64x.go │ │ │ ├── cpu_riscv64.go │ │ │ ├── cpu_s390x.go │ │ │ ├── cpu_s390x.s │ │ │ ├── cpu_wasm.go │ │ │ ├── cpu_x86.go │ │ │ ├── cpu_zos.go │ │ │ ├── cpu_zos_s390x.go │ │ │ ├── endian_big.go │ │ │ ├── endian_little.go │ │ │ ├── hwcap_linux.go │ │ │ ├── parse.go │ │ │ ├── proc_cpuinfo_linux.go │ │ │ ├── runtime_auxv.go │ │ │ ├── runtime_auxv_go121.go │ │ │ ├── syscall_aix_gccgo.go │ │ │ ├── syscall_aix_ppc64_gc.go │ │ │ └── syscall_darwin_x86_gc.go │ │ ├── plan9/ │ │ │ ├── asm.s │ │ │ ├── asm_plan9_386.s │ │ │ ├── asm_plan9_amd64.s │ │ │ ├── asm_plan9_arm.s │ │ │ ├── const_plan9.go │ │ │ ├── dir_plan9.go │ │ │ ├── env_plan9.go │ │ │ ├── errors_plan9.go │ │ │ ├── mkall.sh │ │ │ ├── mkerrors.sh │ │ │ ├── mksysnum_plan9.sh │ │ │ ├── pwd_go15_plan9.go │ │ │ ├── pwd_plan9.go │ │ │ ├── race.go │ │ │ ├── race0.go │ │ │ ├── str.go │ │ │ ├── syscall.go │ │ │ ├── syscall_plan9.go │ │ │ ├── zsyscall_plan9_386.go │ │ │ ├── zsyscall_plan9_amd64.go │ │ │ ├── zsyscall_plan9_arm.go │ │ │ └── zsysnum_plan9.go │ │ ├── unix/ │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── affinity_linux.go │ │ │ ├── aliases.go │ │ │ ├── asm_aix_ppc64.s │ │ │ ├── asm_bsd_386.s │ │ │ ├── asm_bsd_amd64.s │ │ │ ├── asm_bsd_arm.s │ │ │ ├── asm_bsd_arm64.s │ │ │ ├── asm_bsd_ppc64.s │ │ │ ├── asm_bsd_riscv64.s │ │ │ ├── asm_linux_386.s │ │ │ ├── asm_linux_amd64.s │ │ │ ├── asm_linux_arm.s │ │ │ ├── asm_linux_arm64.s │ │ │ ├── asm_linux_loong64.s │ │ │ ├── asm_linux_mips64x.s │ │ │ ├── asm_linux_mipsx.s │ │ │ ├── asm_linux_ppc64x.s │ │ │ ├── asm_linux_riscv64.s │ │ │ ├── asm_linux_s390x.s │ │ │ ├── asm_openbsd_mips64.s │ │ │ ├── asm_solaris_amd64.s │ │ │ ├── asm_zos_s390x.s │ │ │ ├── auxv.go │ │ │ ├── auxv_unsupported.go │ │ │ ├── bluetooth_linux.go │ │ │ ├── bpxsvc_zos.go │ │ │ ├── bpxsvc_zos.s │ │ │ ├── cap_freebsd.go │ │ │ ├── constants.go │ │ │ ├── dev_aix_ppc.go │ │ │ ├── dev_aix_ppc64.go │ │ │ ├── dev_darwin.go │ │ │ ├── dev_dragonfly.go │ │ │ ├── dev_freebsd.go │ │ │ ├── dev_linux.go │ │ │ ├── dev_netbsd.go │ │ │ ├── dev_openbsd.go │ │ │ ├── dev_zos.go │ │ │ ├── dirent.go │ │ │ ├── endian_big.go │ │ │ ├── endian_little.go │ │ │ ├── env_unix.go │ │ │ ├── fcntl.go │ │ │ ├── fcntl_darwin.go │ │ │ ├── fcntl_linux_32bit.go │ │ │ ├── fdset.go │ │ │ ├── gccgo.go │ │ │ ├── gccgo_c.c │ │ │ ├── gccgo_linux_amd64.go │ │ │ ├── ifreq_linux.go │ │ │ ├── ioctl_linux.go │ │ │ ├── ioctl_signed.go │ │ │ ├── ioctl_unsigned.go │ │ │ ├── ioctl_zos.go │ │ │ ├── mkall.sh │ │ │ ├── mkerrors.sh │ │ │ ├── mmap_nomremap.go │ │ │ ├── mremap.go │ │ │ ├── pagesize_unix.go │ │ │ ├── pledge_openbsd.go │ │ │ ├── ptrace_darwin.go │ │ │ ├── ptrace_ios.go │ │ │ ├── race.go │ │ │ ├── race0.go │ │ │ ├── readdirent_getdents.go │ │ │ ├── readdirent_getdirentries.go │ │ │ ├── sockcmsg_dragonfly.go │ │ │ ├── sockcmsg_linux.go │ │ │ ├── sockcmsg_unix.go │ │ │ ├── sockcmsg_unix_other.go │ │ │ ├── sockcmsg_zos.go │ │ │ ├── symaddr_zos_s390x.s │ │ │ ├── syscall.go │ │ │ ├── syscall_aix.go │ │ │ ├── syscall_aix_ppc.go │ │ │ ├── syscall_aix_ppc64.go │ │ │ ├── syscall_bsd.go │ │ │ ├── syscall_darwin.go │ │ │ ├── syscall_darwin_amd64.go │ │ │ ├── syscall_darwin_arm64.go │ │ │ ├── syscall_darwin_libSystem.go │ │ │ ├── syscall_dragonfly.go │ │ │ ├── syscall_dragonfly_amd64.go │ │ │ ├── syscall_freebsd.go │ │ │ ├── syscall_freebsd_386.go │ │ │ ├── syscall_freebsd_amd64.go │ │ │ ├── syscall_freebsd_arm.go │ │ │ ├── syscall_freebsd_arm64.go │ │ │ ├── syscall_freebsd_riscv64.go │ │ │ ├── syscall_hurd.go │ │ │ ├── syscall_hurd_386.go │ │ │ ├── syscall_illumos.go │ │ │ ├── syscall_linux.go │ │ │ ├── syscall_linux_386.go │ │ │ ├── syscall_linux_alarm.go │ │ │ ├── syscall_linux_amd64.go │ │ │ ├── syscall_linux_amd64_gc.go │ │ │ ├── syscall_linux_arm.go │ │ │ ├── syscall_linux_arm64.go │ │ │ ├── syscall_linux_gc.go │ │ │ ├── syscall_linux_gc_386.go │ │ │ ├── syscall_linux_gc_arm.go │ │ │ ├── syscall_linux_gccgo_386.go │ │ │ ├── syscall_linux_gccgo_arm.go │ │ │ ├── syscall_linux_loong64.go │ │ │ ├── syscall_linux_mips64x.go │ │ │ ├── syscall_linux_mipsx.go │ │ │ ├── syscall_linux_ppc.go │ │ │ ├── syscall_linux_ppc64x.go │ │ │ ├── syscall_linux_riscv64.go │ │ │ ├── syscall_linux_s390x.go │ │ │ ├── syscall_linux_sparc64.go │ │ │ ├── syscall_netbsd.go │ │ │ ├── syscall_netbsd_386.go │ │ │ ├── syscall_netbsd_amd64.go │ │ │ ├── syscall_netbsd_arm.go │ │ │ ├── syscall_netbsd_arm64.go │ │ │ ├── syscall_openbsd.go │ │ │ ├── syscall_openbsd_386.go │ │ │ ├── syscall_openbsd_amd64.go │ │ │ ├── syscall_openbsd_arm.go │ │ │ ├── syscall_openbsd_arm64.go │ │ │ ├── syscall_openbsd_libc.go │ │ │ ├── syscall_openbsd_mips64.go │ │ │ ├── syscall_openbsd_ppc64.go │ │ │ ├── syscall_openbsd_riscv64.go │ │ │ ├── syscall_solaris.go │ │ │ ├── syscall_solaris_amd64.go │ │ │ ├── syscall_unix.go │ │ │ ├── syscall_unix_gc.go │ │ │ ├── syscall_unix_gc_ppc64x.go │ │ │ ├── syscall_zos_s390x.go │ │ │ ├── sysvshm_linux.go │ │ │ ├── sysvshm_unix.go │ │ │ ├── sysvshm_unix_other.go │ │ │ ├── timestruct.go │ │ │ ├── unveil_openbsd.go │ │ │ ├── vgetrandom_linux.go │ │ │ ├── vgetrandom_unsupported.go │ │ │ ├── xattr_bsd.go │ │ │ ├── zerrors_aix_ppc.go │ │ │ ├── zerrors_aix_ppc64.go │ │ │ ├── zerrors_darwin_amd64.go │ │ │ ├── zerrors_darwin_arm64.go │ │ │ ├── zerrors_dragonfly_amd64.go │ │ │ ├── zerrors_freebsd_386.go │ │ │ ├── zerrors_freebsd_amd64.go │ │ │ ├── zerrors_freebsd_arm.go │ │ │ ├── zerrors_freebsd_arm64.go │ │ │ ├── zerrors_freebsd_riscv64.go │ │ │ ├── zerrors_linux.go │ │ │ ├── zerrors_linux_386.go │ │ │ ├── zerrors_linux_amd64.go │ │ │ ├── zerrors_linux_arm.go │ │ │ ├── zerrors_linux_arm64.go │ │ │ ├── zerrors_linux_loong64.go │ │ │ ├── zerrors_linux_mips.go │ │ │ ├── zerrors_linux_mips64.go │ │ │ ├── zerrors_linux_mips64le.go │ │ │ ├── zerrors_linux_mipsle.go │ │ │ ├── zerrors_linux_ppc.go │ │ │ ├── zerrors_linux_ppc64.go │ │ │ ├── zerrors_linux_ppc64le.go │ │ │ ├── zerrors_linux_riscv64.go │ │ │ ├── zerrors_linux_s390x.go │ │ │ ├── zerrors_linux_sparc64.go │ │ │ ├── zerrors_netbsd_386.go │ │ │ ├── zerrors_netbsd_amd64.go │ │ │ ├── zerrors_netbsd_arm.go │ │ │ ├── zerrors_netbsd_arm64.go │ │ │ ├── zerrors_openbsd_386.go │ │ │ ├── zerrors_openbsd_amd64.go │ │ │ ├── zerrors_openbsd_arm.go │ │ │ ├── zerrors_openbsd_arm64.go │ │ │ ├── zerrors_openbsd_mips64.go │ │ │ ├── zerrors_openbsd_ppc64.go │ │ │ ├── zerrors_openbsd_riscv64.go │ │ │ ├── zerrors_solaris_amd64.go │ │ │ ├── zerrors_zos_s390x.go │ │ │ ├── zptrace_armnn_linux.go │ │ │ ├── zptrace_linux_arm64.go │ │ │ ├── zptrace_mipsnn_linux.go │ │ │ ├── zptrace_mipsnnle_linux.go │ │ │ ├── zptrace_x86_linux.go │ │ │ ├── zsymaddr_zos_s390x.s │ │ │ ├── zsyscall_aix_ppc.go │ │ │ ├── zsyscall_aix_ppc64.go │ │ │ ├── zsyscall_aix_ppc64_gc.go │ │ │ ├── zsyscall_aix_ppc64_gccgo.go │ │ │ ├── zsyscall_darwin_amd64.go │ │ │ ├── zsyscall_darwin_amd64.s │ │ │ ├── zsyscall_darwin_arm64.go │ │ │ ├── zsyscall_darwin_arm64.s │ │ │ ├── zsyscall_dragonfly_amd64.go │ │ │ ├── zsyscall_freebsd_386.go │ │ │ ├── zsyscall_freebsd_amd64.go │ │ │ ├── zsyscall_freebsd_arm.go │ │ │ ├── zsyscall_freebsd_arm64.go │ │ │ ├── zsyscall_freebsd_riscv64.go │ │ │ ├── zsyscall_illumos_amd64.go │ │ │ ├── zsyscall_linux.go │ │ │ ├── zsyscall_linux_386.go │ │ │ ├── zsyscall_linux_amd64.go │ │ │ ├── zsyscall_linux_arm.go │ │ │ ├── zsyscall_linux_arm64.go │ │ │ ├── zsyscall_linux_loong64.go │ │ │ ├── zsyscall_linux_mips.go │ │ │ ├── zsyscall_linux_mips64.go │ │ │ ├── zsyscall_linux_mips64le.go │ │ │ ├── zsyscall_linux_mipsle.go │ │ │ ├── zsyscall_linux_ppc.go │ │ │ ├── zsyscall_linux_ppc64.go │ │ │ ├── zsyscall_linux_ppc64le.go │ │ │ ├── zsyscall_linux_riscv64.go │ │ │ ├── zsyscall_linux_s390x.go │ │ │ ├── zsyscall_linux_sparc64.go │ │ │ ├── zsyscall_netbsd_386.go │ │ │ ├── zsyscall_netbsd_amd64.go │ │ │ ├── zsyscall_netbsd_arm.go │ │ │ ├── zsyscall_netbsd_arm64.go │ │ │ ├── zsyscall_openbsd_386.go │ │ │ ├── zsyscall_openbsd_386.s │ │ │ ├── zsyscall_openbsd_amd64.go │ │ │ ├── zsyscall_openbsd_amd64.s │ │ │ ├── zsyscall_openbsd_arm.go │ │ │ ├── zsyscall_openbsd_arm.s │ │ │ ├── zsyscall_openbsd_arm64.go │ │ │ ├── zsyscall_openbsd_arm64.s │ │ │ ├── zsyscall_openbsd_mips64.go │ │ │ ├── zsyscall_openbsd_mips64.s │ │ │ ├── zsyscall_openbsd_ppc64.go │ │ │ ├── zsyscall_openbsd_ppc64.s │ │ │ ├── zsyscall_openbsd_riscv64.go │ │ │ ├── zsyscall_openbsd_riscv64.s │ │ │ ├── zsyscall_solaris_amd64.go │ │ │ ├── zsyscall_zos_s390x.go │ │ │ ├── zsysctl_openbsd_386.go │ │ │ ├── zsysctl_openbsd_amd64.go │ │ │ ├── zsysctl_openbsd_arm.go │ │ │ ├── zsysctl_openbsd_arm64.go │ │ │ ├── zsysctl_openbsd_mips64.go │ │ │ ├── zsysctl_openbsd_ppc64.go │ │ │ ├── zsysctl_openbsd_riscv64.go │ │ │ ├── zsysnum_darwin_amd64.go │ │ │ ├── zsysnum_darwin_arm64.go │ │ │ ├── zsysnum_dragonfly_amd64.go │ │ │ ├── zsysnum_freebsd_386.go │ │ │ ├── zsysnum_freebsd_amd64.go │ │ │ ├── zsysnum_freebsd_arm.go │ │ │ ├── zsysnum_freebsd_arm64.go │ │ │ ├── zsysnum_freebsd_riscv64.go │ │ │ ├── zsysnum_linux_386.go │ │ │ ├── zsysnum_linux_amd64.go │ │ │ ├── zsysnum_linux_arm.go │ │ │ ├── zsysnum_linux_arm64.go │ │ │ ├── zsysnum_linux_loong64.go │ │ │ ├── zsysnum_linux_mips.go │ │ │ ├── zsysnum_linux_mips64.go │ │ │ ├── zsysnum_linux_mips64le.go │ │ │ ├── zsysnum_linux_mipsle.go │ │ │ ├── zsysnum_linux_ppc.go │ │ │ ├── zsysnum_linux_ppc64.go │ │ │ ├── zsysnum_linux_ppc64le.go │ │ │ ├── zsysnum_linux_riscv64.go │ │ │ ├── zsysnum_linux_s390x.go │ │ │ ├── zsysnum_linux_sparc64.go │ │ │ ├── zsysnum_netbsd_386.go │ │ │ ├── zsysnum_netbsd_amd64.go │ │ │ ├── zsysnum_netbsd_arm.go │ │ │ ├── zsysnum_netbsd_arm64.go │ │ │ ├── zsysnum_openbsd_386.go │ │ │ ├── zsysnum_openbsd_amd64.go │ │ │ ├── zsysnum_openbsd_arm.go │ │ │ ├── zsysnum_openbsd_arm64.go │ │ │ ├── zsysnum_openbsd_mips64.go │ │ │ ├── zsysnum_openbsd_ppc64.go │ │ │ ├── zsysnum_openbsd_riscv64.go │ │ │ ├── zsysnum_zos_s390x.go │ │ │ ├── ztypes_aix_ppc.go │ │ │ ├── ztypes_aix_ppc64.go │ │ │ ├── ztypes_darwin_amd64.go │ │ │ ├── ztypes_darwin_arm64.go │ │ │ ├── ztypes_dragonfly_amd64.go │ │ │ ├── ztypes_freebsd_386.go │ │ │ ├── ztypes_freebsd_amd64.go │ │ │ ├── ztypes_freebsd_arm.go │ │ │ ├── ztypes_freebsd_arm64.go │ │ │ ├── ztypes_freebsd_riscv64.go │ │ │ ├── ztypes_linux.go │ │ │ ├── ztypes_linux_386.go │ │ │ ├── ztypes_linux_amd64.go │ │ │ ├── ztypes_linux_arm.go │ │ │ ├── ztypes_linux_arm64.go │ │ │ ├── ztypes_linux_loong64.go │ │ │ ├── ztypes_linux_mips.go │ │ │ ├── ztypes_linux_mips64.go │ │ │ ├── ztypes_linux_mips64le.go │ │ │ ├── ztypes_linux_mipsle.go │ │ │ ├── ztypes_linux_ppc.go │ │ │ ├── ztypes_linux_ppc64.go │ │ │ ├── ztypes_linux_ppc64le.go │ │ │ ├── ztypes_linux_riscv64.go │ │ │ ├── ztypes_linux_s390x.go │ │ │ ├── ztypes_linux_sparc64.go │ │ │ ├── ztypes_netbsd_386.go │ │ │ ├── ztypes_netbsd_amd64.go │ │ │ ├── ztypes_netbsd_arm.go │ │ │ ├── ztypes_netbsd_arm64.go │ │ │ ├── ztypes_openbsd_386.go │ │ │ ├── ztypes_openbsd_amd64.go │ │ │ ├── ztypes_openbsd_arm.go │ │ │ ├── ztypes_openbsd_arm64.go │ │ │ ├── ztypes_openbsd_mips64.go │ │ │ ├── ztypes_openbsd_ppc64.go │ │ │ ├── ztypes_openbsd_riscv64.go │ │ │ ├── ztypes_solaris_amd64.go │ │ │ └── ztypes_zos_s390x.go │ │ └── windows/ │ │ ├── aliases.go │ │ ├── dll_windows.go │ │ ├── env_windows.go │ │ ├── eventlog.go │ │ ├── exec_windows.go │ │ ├── memory_windows.go │ │ ├── mkerrors.bash │ │ ├── mkknownfolderids.bash │ │ ├── mksyscall.go │ │ ├── race.go │ │ ├── race0.go │ │ ├── registry/ │ │ │ ├── key.go │ │ │ ├── mksyscall.go │ │ │ ├── syscall.go │ │ │ ├── value.go │ │ │ └── zsyscall_windows.go │ │ ├── security_windows.go │ │ ├── service.go │ │ ├── setupapi_windows.go │ │ ├── str.go │ │ ├── svc/ │ │ │ ├── mgr/ │ │ │ │ ├── config.go │ │ │ │ ├── mgr.go │ │ │ │ ├── recovery.go │ │ │ │ └── service.go │ │ │ ├── security.go │ │ │ └── service.go │ │ ├── syscall.go │ │ ├── syscall_windows.go │ │ ├── types_windows.go │ │ ├── types_windows_386.go │ │ ├── types_windows_amd64.go │ │ ├── types_windows_arm.go │ │ ├── types_windows_arm64.go │ │ ├── zerrors_windows.go │ │ ├── zknownfolderids_windows.go │ │ └── zsyscall_windows.go │ ├── term/ │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── README.md │ │ ├── codereview.cfg │ │ ├── term.go │ │ ├── term_plan9.go │ │ ├── term_unix.go │ │ ├── term_unix_bsd.go │ │ ├── term_unix_other.go │ │ ├── term_unsupported.go │ │ ├── term_windows.go │ │ └── terminal.go │ ├── text/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── cases/ │ │ │ ├── cases.go │ │ │ ├── context.go │ │ │ ├── fold.go │ │ │ ├── icu.go │ │ │ ├── info.go │ │ │ ├── map.go │ │ │ ├── tables10.0.0.go │ │ │ ├── tables11.0.0.go │ │ │ ├── tables12.0.0.go │ │ │ ├── tables13.0.0.go │ │ │ ├── tables15.0.0.go │ │ │ ├── tables9.0.0.go │ │ │ └── trieval.go │ │ ├── encoding/ │ │ │ ├── charmap/ │ │ │ │ ├── charmap.go │ │ │ │ └── tables.go │ │ │ ├── encoding.go │ │ │ ├── htmlindex/ │ │ │ │ ├── htmlindex.go │ │ │ │ ├── map.go │ │ │ │ └── tables.go │ │ │ ├── internal/ │ │ │ │ ├── identifier/ │ │ │ │ │ ├── identifier.go │ │ │ │ │ └── mib.go │ │ │ │ └── internal.go │ │ │ ├── japanese/ │ │ │ │ ├── all.go │ │ │ │ ├── eucjp.go │ │ │ │ ├── iso2022jp.go │ │ │ │ ├── shiftjis.go │ │ │ │ └── tables.go │ │ │ ├── korean/ │ │ │ │ ├── euckr.go │ │ │ │ └── tables.go │ │ │ ├── simplifiedchinese/ │ │ │ │ ├── all.go │ │ │ │ ├── gbk.go │ │ │ │ ├── hzgb2312.go │ │ │ │ └── tables.go │ │ │ ├── traditionalchinese/ │ │ │ │ ├── big5.go │ │ │ │ └── tables.go │ │ │ └── unicode/ │ │ │ ├── override.go │ │ │ └── unicode.go │ │ ├── feature/ │ │ │ └── plural/ │ │ │ ├── common.go │ │ │ ├── message.go │ │ │ ├── plural.go │ │ │ └── tables.go │ │ ├── internal/ │ │ │ ├── catmsg/ │ │ │ │ ├── catmsg.go │ │ │ │ ├── codec.go │ │ │ │ └── varint.go │ │ │ ├── format/ │ │ │ │ ├── format.go │ │ │ │ └── parser.go │ │ │ ├── internal.go │ │ │ ├── language/ │ │ │ │ ├── common.go │ │ │ │ ├── compact/ │ │ │ │ │ ├── compact.go │ │ │ │ │ ├── language.go │ │ │ │ │ ├── parents.go │ │ │ │ │ ├── tables.go │ │ │ │ │ └── tags.go │ │ │ │ ├── compact.go │ │ │ │ ├── compose.go │ │ │ │ ├── coverage.go │ │ │ │ ├── language.go │ │ │ │ ├── lookup.go │ │ │ │ ├── match.go │ │ │ │ ├── parse.go │ │ │ │ ├── tables.go │ │ │ │ └── tags.go │ │ │ ├── match.go │ │ │ ├── number/ │ │ │ │ ├── common.go │ │ │ │ ├── decimal.go │ │ │ │ ├── format.go │ │ │ │ ├── number.go │ │ │ │ ├── pattern.go │ │ │ │ ├── roundingmode_string.go │ │ │ │ └── tables.go │ │ │ ├── stringset/ │ │ │ │ └── set.go │ │ │ ├── tag/ │ │ │ │ └── tag.go │ │ │ └── utf8internal/ │ │ │ └── utf8internal.go │ │ ├── language/ │ │ │ ├── coverage.go │ │ │ ├── doc.go │ │ │ ├── language.go │ │ │ ├── match.go │ │ │ ├── parse.go │ │ │ ├── tables.go │ │ │ └── tags.go │ │ ├── message/ │ │ │ ├── catalog/ │ │ │ │ ├── catalog.go │ │ │ │ ├── dict.go │ │ │ │ ├── go19.go │ │ │ │ └── gopre19.go │ │ │ ├── catalog.go │ │ │ ├── doc.go │ │ │ ├── format.go │ │ │ ├── message.go │ │ │ └── print.go │ │ ├── runes/ │ │ │ ├── cond.go │ │ │ └── runes.go │ │ ├── secure/ │ │ │ └── bidirule/ │ │ │ ├── bidirule.go │ │ │ ├── bidirule10.0.0.go │ │ │ └── bidirule9.0.0.go │ │ ├── transform/ │ │ │ └── transform.go │ │ ├── unicode/ │ │ │ ├── bidi/ │ │ │ │ ├── bidi.go │ │ │ │ ├── bracket.go │ │ │ │ ├── core.go │ │ │ │ ├── prop.go │ │ │ │ ├── tables10.0.0.go │ │ │ │ ├── tables11.0.0.go │ │ │ │ ├── tables12.0.0.go │ │ │ │ ├── tables13.0.0.go │ │ │ │ ├── tables15.0.0.go │ │ │ │ ├── tables9.0.0.go │ │ │ │ └── trieval.go │ │ │ └── norm/ │ │ │ ├── composition.go │ │ │ ├── forminfo.go │ │ │ ├── input.go │ │ │ ├── iter.go │ │ │ ├── normalize.go │ │ │ ├── readwriter.go │ │ │ ├── tables10.0.0.go │ │ │ ├── tables11.0.0.go │ │ │ ├── tables12.0.0.go │ │ │ ├── tables13.0.0.go │ │ │ ├── tables15.0.0.go │ │ │ ├── tables9.0.0.go │ │ │ ├── transform.go │ │ │ └── trie.go │ │ └── width/ │ │ ├── kind_string.go │ │ ├── tables10.0.0.go │ │ ├── tables11.0.0.go │ │ ├── tables12.0.0.go │ │ ├── tables13.0.0.go │ │ ├── tables15.0.0.go │ │ ├── tables9.0.0.go │ │ ├── transform.go │ │ ├── trieval.go │ │ └── width.go │ ├── time/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ └── rate/ │ │ ├── rate.go │ │ └── sometimes.go │ └── tools/ │ ├── LICENSE │ ├── PATENTS │ ├── cover/ │ │ └── profile.go │ ├── go/ │ │ ├── ast/ │ │ │ └── inspector/ │ │ │ ├── inspector.go │ │ │ ├── iter.go │ │ │ └── typeof.go │ │ ├── gcexportdata/ │ │ │ ├── gcexportdata.go │ │ │ └── importer.go │ │ ├── packages/ │ │ │ ├── doc.go │ │ │ ├── external.go │ │ │ ├── golist.go │ │ │ ├── golist_overlay.go │ │ │ ├── loadmode_string.go │ │ │ ├── packages.go │ │ │ └── visit.go │ │ └── types/ │ │ ├── objectpath/ │ │ │ └── objectpath.go │ │ └── typeutil/ │ │ ├── callee.go │ │ ├── imports.go │ │ ├── map.go │ │ ├── methodsetcache.go │ │ └── ui.go │ └── internal/ │ ├── aliases/ │ │ ├── aliases.go │ │ └── aliases_go122.go │ ├── event/ │ │ ├── core/ │ │ │ ├── event.go │ │ │ ├── export.go │ │ │ └── fast.go │ │ ├── doc.go │ │ ├── event.go │ │ ├── keys/ │ │ │ ├── keys.go │ │ │ ├── standard.go │ │ │ └── util.go │ │ └── label/ │ │ └── label.go │ ├── gcimporter/ │ │ ├── bimport.go │ │ ├── exportdata.go │ │ ├── gcimporter.go │ │ ├── iexport.go │ │ ├── iimport.go │ │ ├── iimport_go122.go │ │ ├── predeclared.go │ │ ├── support.go │ │ └── ureader_yes.go │ ├── gocommand/ │ │ ├── invoke.go │ │ ├── vendor.go │ │ └── version.go │ ├── packagesinternal/ │ │ └── packages.go │ ├── pkgbits/ │ │ ├── codes.go │ │ ├── decoder.go │ │ ├── doc.go │ │ ├── encoder.go │ │ ├── flags.go │ │ ├── reloc.go │ │ ├── support.go │ │ ├── sync.go │ │ ├── syncmarker_string.go │ │ └── version.go │ ├── stdlib/ │ │ ├── manifest.go │ │ └── stdlib.go │ ├── typeparams/ │ │ ├── common.go │ │ ├── coretype.go │ │ ├── free.go │ │ ├── normalize.go │ │ ├── termlist.go │ │ └── typeterm.go │ ├── typesinternal/ │ │ ├── element.go │ │ ├── errorcode.go │ │ ├── errorcode_string.go │ │ ├── qualifier.go │ │ ├── recv.go │ │ ├── toonew.go │ │ ├── types.go │ │ └── zerovalue.go │ └── versions/ │ ├── features.go │ ├── gover.go │ ├── types.go │ └── versions.go ├── golang.zx2c4.com/ │ ├── wintun/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── dll.go │ │ ├── session.go │ │ └── wintun.go │ └── wireguard/ │ └── windows/ │ ├── COPYING │ └── tunnel/ │ └── winipcfg/ │ ├── interface_change_handler.go │ ├── luid.go │ ├── mksyscall.go │ ├── netsh.go │ ├── route_change_handler.go │ ├── types.go │ ├── types_32.go │ ├── types_64.go │ ├── types_test_32.go │ ├── types_test_64.go │ ├── unicast_address_change_handler.go │ ├── winipcfg.go │ └── zwinipcfg_windows.go ├── gomodules.xyz/ │ └── jsonpatch/ │ └── v2/ │ ├── LICENSE │ └── jsonpatch.go ├── google.golang.org/ │ ├── genproto/ │ │ └── googleapis/ │ │ ├── api/ │ │ │ ├── LICENSE │ │ │ ├── annotations/ │ │ │ │ ├── annotations.pb.go │ │ │ │ ├── client.pb.go │ │ │ │ ├── field_behavior.pb.go │ │ │ │ ├── field_info.pb.go │ │ │ │ ├── http.pb.go │ │ │ │ ├── resource.pb.go │ │ │ │ └── routing.pb.go │ │ │ ├── expr/ │ │ │ │ └── v1alpha1/ │ │ │ │ ├── checked.pb.go │ │ │ │ ├── eval.pb.go │ │ │ │ ├── explain.pb.go │ │ │ │ ├── syntax.pb.go │ │ │ │ └── value.pb.go │ │ │ ├── httpbody/ │ │ │ │ └── httpbody.pb.go │ │ │ └── launch_stage.pb.go │ │ └── rpc/ │ │ ├── LICENSE │ │ ├── errdetails/ │ │ │ └── error_details.pb.go │ │ └── status/ │ │ └── status.pb.go │ ├── grpc/ │ │ ├── AUTHORS │ │ ├── CODE-OF-CONDUCT.md │ │ ├── CONTRIBUTING.md │ │ ├── GOVERNANCE.md │ │ ├── LICENSE │ │ ├── MAINTAINERS.md │ │ ├── Makefile │ │ ├── NOTICE.txt │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── attributes/ │ │ │ └── attributes.go │ │ ├── backoff/ │ │ │ └── backoff.go │ │ ├── backoff.go │ │ ├── balancer/ │ │ │ ├── balancer.go │ │ │ ├── base/ │ │ │ │ ├── balancer.go │ │ │ │ └── base.go │ │ │ ├── conn_state_evaluator.go │ │ │ ├── grpclb/ │ │ │ │ └── state/ │ │ │ │ └── state.go │ │ │ ├── pickfirst/ │ │ │ │ ├── internal/ │ │ │ │ │ └── internal.go │ │ │ │ ├── pickfirst.go │ │ │ │ └── pickfirstleaf/ │ │ │ │ └── pickfirstleaf.go │ │ │ ├── roundrobin/ │ │ │ │ └── roundrobin.go │ │ │ └── subconn.go │ │ ├── balancer_wrapper.go │ │ ├── binarylog/ │ │ │ └── grpc_binarylog_v1/ │ │ │ └── binarylog.pb.go │ │ ├── call.go │ │ ├── channelz/ │ │ │ └── channelz.go │ │ ├── clientconn.go │ │ ├── codec.go │ │ ├── codes/ │ │ │ ├── code_string.go │ │ │ └── codes.go │ │ ├── connectivity/ │ │ │ └── connectivity.go │ │ ├── credentials/ │ │ │ ├── credentials.go │ │ │ ├── insecure/ │ │ │ │ └── insecure.go │ │ │ └── tls.go │ │ ├── dialoptions.go │ │ ├── doc.go │ │ ├── encoding/ │ │ │ ├── encoding.go │ │ │ ├── encoding_v2.go │ │ │ ├── gzip/ │ │ │ │ └── gzip.go │ │ │ └── proto/ │ │ │ └── proto.go │ │ ├── experimental/ │ │ │ └── stats/ │ │ │ ├── metricregistry.go │ │ │ └── metrics.go │ │ ├── grpclog/ │ │ │ ├── component.go │ │ │ ├── grpclog.go │ │ │ ├── internal/ │ │ │ │ ├── grpclog.go │ │ │ │ ├── logger.go │ │ │ │ └── loggerv2.go │ │ │ ├── logger.go │ │ │ └── loggerv2.go │ │ ├── health/ │ │ │ ├── client.go │ │ │ ├── grpc_health_v1/ │ │ │ │ ├── health.pb.go │ │ │ │ └── health_grpc.pb.go │ │ │ ├── logging.go │ │ │ └── server.go │ │ ├── interceptor.go │ │ ├── internal/ │ │ │ ├── backoff/ │ │ │ │ └── backoff.go │ │ │ ├── balancer/ │ │ │ │ └── gracefulswitch/ │ │ │ │ ├── config.go │ │ │ │ └── gracefulswitch.go │ │ │ ├── balancerload/ │ │ │ │ └── load.go │ │ │ ├── binarylog/ │ │ │ │ ├── binarylog.go │ │ │ │ ├── binarylog_testutil.go │ │ │ │ ├── env_config.go │ │ │ │ ├── method_logger.go │ │ │ │ └── sink.go │ │ │ ├── buffer/ │ │ │ │ └── unbounded.go │ │ │ ├── channelz/ │ │ │ │ ├── channel.go │ │ │ │ ├── channelmap.go │ │ │ │ ├── funcs.go │ │ │ │ ├── logging.go │ │ │ │ ├── server.go │ │ │ │ ├── socket.go │ │ │ │ ├── subchannel.go │ │ │ │ ├── syscall_linux.go │ │ │ │ ├── syscall_nonlinux.go │ │ │ │ └── trace.go │ │ │ ├── credentials/ │ │ │ │ ├── credentials.go │ │ │ │ ├── spiffe.go │ │ │ │ ├── syscallconn.go │ │ │ │ └── util.go │ │ │ ├── envconfig/ │ │ │ │ ├── envconfig.go │ │ │ │ ├── observability.go │ │ │ │ └── xds.go │ │ │ ├── experimental.go │ │ │ ├── grpclog/ │ │ │ │ └── prefix_logger.go │ │ │ ├── grpcsync/ │ │ │ │ ├── callback_serializer.go │ │ │ │ ├── event.go │ │ │ │ ├── oncefunc.go │ │ │ │ └── pubsub.go │ │ │ ├── grpcutil/ │ │ │ │ ├── compressor.go │ │ │ │ ├── encode_duration.go │ │ │ │ ├── grpcutil.go │ │ │ │ ├── metadata.go │ │ │ │ ├── method.go │ │ │ │ └── regex.go │ │ │ ├── idle/ │ │ │ │ └── idle.go │ │ │ ├── internal.go │ │ │ ├── metadata/ │ │ │ │ └── metadata.go │ │ │ ├── pretty/ │ │ │ │ └── pretty.go │ │ │ ├── resolver/ │ │ │ │ ├── config_selector.go │ │ │ │ ├── dns/ │ │ │ │ │ ├── dns_resolver.go │ │ │ │ │ └── internal/ │ │ │ │ │ └── internal.go │ │ │ │ ├── passthrough/ │ │ │ │ │ └── passthrough.go │ │ │ │ └── unix/ │ │ │ │ └── unix.go │ │ │ ├── serviceconfig/ │ │ │ │ ├── duration.go │ │ │ │ └── serviceconfig.go │ │ │ ├── stats/ │ │ │ │ ├── labels.go │ │ │ │ └── metrics_recorder_list.go │ │ │ ├── status/ │ │ │ │ └── status.go │ │ │ ├── syscall/ │ │ │ │ ├── syscall_linux.go │ │ │ │ └── syscall_nonlinux.go │ │ │ ├── tcp_keepalive_others.go │ │ │ ├── tcp_keepalive_unix.go │ │ │ ├── tcp_keepalive_windows.go │ │ │ └── transport/ │ │ │ ├── bdp_estimator.go │ │ │ ├── client_stream.go │ │ │ ├── controlbuf.go │ │ │ ├── defaults.go │ │ │ ├── flowcontrol.go │ │ │ ├── handler_server.go │ │ │ ├── http2_client.go │ │ │ ├── http2_server.go │ │ │ ├── http_util.go │ │ │ ├── logging.go │ │ │ ├── networktype/ │ │ │ │ └── networktype.go │ │ │ ├── proxy.go │ │ │ ├── server_stream.go │ │ │ └── transport.go │ │ ├── keepalive/ │ │ │ └── keepalive.go │ │ ├── mem/ │ │ │ ├── buffer_pool.go │ │ │ ├── buffer_slice.go │ │ │ └── buffers.go │ │ ├── metadata/ │ │ │ └── metadata.go │ │ ├── peer/ │ │ │ └── peer.go │ │ ├── picker_wrapper.go │ │ ├── preloader.go │ │ ├── reflection/ │ │ │ ├── README.md │ │ │ ├── adapt.go │ │ │ ├── grpc_reflection_v1/ │ │ │ │ ├── reflection.pb.go │ │ │ │ └── reflection_grpc.pb.go │ │ │ ├── grpc_reflection_v1alpha/ │ │ │ │ ├── reflection.pb.go │ │ │ │ └── reflection_grpc.pb.go │ │ │ ├── internal/ │ │ │ │ └── internal.go │ │ │ └── serverreflection.go │ │ ├── resolver/ │ │ │ ├── dns/ │ │ │ │ └── dns_resolver.go │ │ │ ├── manual/ │ │ │ │ └── manual.go │ │ │ ├── map.go │ │ │ └── resolver.go │ │ ├── resolver_wrapper.go │ │ ├── rpc_util.go │ │ ├── server.go │ │ ├── service_config.go │ │ ├── serviceconfig/ │ │ │ └── serviceconfig.go │ │ ├── stats/ │ │ │ ├── handlers.go │ │ │ ├── metrics.go │ │ │ └── stats.go │ │ ├── status/ │ │ │ └── status.go │ │ ├── stream.go │ │ ├── stream_interfaces.go │ │ ├── tap/ │ │ │ └── tap.go │ │ ├── trace.go │ │ ├── trace_notrace.go │ │ ├── trace_withtrace.go │ │ └── version.go │ └── protobuf/ │ ├── LICENSE │ ├── PATENTS │ ├── encoding/ │ │ ├── protodelim/ │ │ │ └── protodelim.go │ │ ├── protojson/ │ │ │ ├── decode.go │ │ │ ├── doc.go │ │ │ ├── encode.go │ │ │ └── well_known_types.go │ │ ├── prototext/ │ │ │ ├── decode.go │ │ │ ├── doc.go │ │ │ └── encode.go │ │ └── protowire/ │ │ └── wire.go │ ├── internal/ │ │ ├── descfmt/ │ │ │ └── stringer.go │ │ ├── descopts/ │ │ │ └── options.go │ │ ├── detrand/ │ │ │ └── rand.go │ │ ├── editiondefaults/ │ │ │ ├── defaults.go │ │ │ └── editions_defaults.binpb │ │ ├── editionssupport/ │ │ │ └── editions.go │ │ ├── encoding/ │ │ │ ├── defval/ │ │ │ │ └── default.go │ │ │ ├── json/ │ │ │ │ ├── decode.go │ │ │ │ ├── decode_number.go │ │ │ │ ├── decode_string.go │ │ │ │ ├── decode_token.go │ │ │ │ └── encode.go │ │ │ ├── messageset/ │ │ │ │ └── messageset.go │ │ │ ├── tag/ │ │ │ │ └── tag.go │ │ │ └── text/ │ │ │ ├── decode.go │ │ │ ├── decode_number.go │ │ │ ├── decode_string.go │ │ │ ├── decode_token.go │ │ │ ├── doc.go │ │ │ └── encode.go │ │ ├── errors/ │ │ │ ├── errors.go │ │ │ ├── is_go112.go │ │ │ └── is_go113.go │ │ ├── filedesc/ │ │ │ ├── build.go │ │ │ ├── desc.go │ │ │ ├── desc_init.go │ │ │ ├── desc_lazy.go │ │ │ ├── desc_list.go │ │ │ ├── desc_list_gen.go │ │ │ ├── editions.go │ │ │ └── placeholder.go │ │ ├── filetype/ │ │ │ └── build.go │ │ ├── flags/ │ │ │ ├── flags.go │ │ │ ├── proto_legacy_disable.go │ │ │ └── proto_legacy_enable.go │ │ ├── genid/ │ │ │ ├── any_gen.go │ │ │ ├── api_gen.go │ │ │ ├── descriptor_gen.go │ │ │ ├── doc.go │ │ │ ├── duration_gen.go │ │ │ ├── empty_gen.go │ │ │ ├── field_mask_gen.go │ │ │ ├── go_features_gen.go │ │ │ ├── goname.go │ │ │ ├── map_entry.go │ │ │ ├── source_context_gen.go │ │ │ ├── struct_gen.go │ │ │ ├── timestamp_gen.go │ │ │ ├── type_gen.go │ │ │ ├── wrappers.go │ │ │ └── wrappers_gen.go │ │ ├── impl/ │ │ │ ├── api_export.go │ │ │ ├── checkinit.go │ │ │ ├── codec_extension.go │ │ │ ├── codec_field.go │ │ │ ├── codec_gen.go │ │ │ ├── codec_map.go │ │ │ ├── codec_map_go111.go │ │ │ ├── codec_map_go112.go │ │ │ ├── codec_message.go │ │ │ ├── codec_messageset.go │ │ │ ├── codec_tables.go │ │ │ ├── codec_unsafe.go │ │ │ ├── convert.go │ │ │ ├── convert_list.go │ │ │ ├── convert_map.go │ │ │ ├── decode.go │ │ │ ├── encode.go │ │ │ ├── enum.go │ │ │ ├── equal.go │ │ │ ├── extension.go │ │ │ ├── legacy_enum.go │ │ │ ├── legacy_export.go │ │ │ ├── legacy_extension.go │ │ │ ├── legacy_file.go │ │ │ ├── legacy_message.go │ │ │ ├── merge.go │ │ │ ├── merge_gen.go │ │ │ ├── message.go │ │ │ ├── message_reflect.go │ │ │ ├── message_reflect_field.go │ │ │ ├── message_reflect_gen.go │ │ │ ├── pointer_unsafe.go │ │ │ ├── validate.go │ │ │ └── weak.go │ │ ├── order/ │ │ │ ├── order.go │ │ │ └── range.go │ │ ├── pragma/ │ │ │ └── pragma.go │ │ ├── set/ │ │ │ └── ints.go │ │ ├── strs/ │ │ │ ├── strings.go │ │ │ ├── strings_unsafe_go120.go │ │ │ └── strings_unsafe_go121.go │ │ └── version/ │ │ └── version.go │ ├── proto/ │ │ ├── checkinit.go │ │ ├── decode.go │ │ ├── decode_gen.go │ │ ├── doc.go │ │ ├── encode.go │ │ ├── encode_gen.go │ │ ├── equal.go │ │ ├── extension.go │ │ ├── merge.go │ │ ├── messageset.go │ │ ├── proto.go │ │ ├── proto_methods.go │ │ ├── proto_reflect.go │ │ ├── reset.go │ │ ├── size.go │ │ ├── size_gen.go │ │ └── wrappers.go │ ├── protoadapt/ │ │ └── convert.go │ ├── reflect/ │ │ ├── protodesc/ │ │ │ ├── desc.go │ │ │ ├── desc_init.go │ │ │ ├── desc_resolve.go │ │ │ ├── desc_validate.go │ │ │ ├── editions.go │ │ │ └── proto.go │ │ ├── protoreflect/ │ │ │ ├── methods.go │ │ │ ├── proto.go │ │ │ ├── source.go │ │ │ ├── source_gen.go │ │ │ ├── type.go │ │ │ ├── value.go │ │ │ ├── value_equal.go │ │ │ ├── value_union.go │ │ │ ├── value_unsafe_go120.go │ │ │ └── value_unsafe_go121.go │ │ └── protoregistry/ │ │ └── registry.go │ ├── runtime/ │ │ ├── protoiface/ │ │ │ ├── legacy.go │ │ │ └── methods.go │ │ └── protoimpl/ │ │ ├── impl.go │ │ └── version.go │ └── types/ │ ├── descriptorpb/ │ │ └── descriptor.pb.go │ ├── dynamicpb/ │ │ ├── dynamic.go │ │ └── types.go │ ├── gofeaturespb/ │ │ └── go_features.pb.go │ └── known/ │ ├── anypb/ │ │ └── any.pb.go │ ├── durationpb/ │ │ └── duration.pb.go │ ├── emptypb/ │ │ └── empty.pb.go │ ├── fieldmaskpb/ │ │ └── field_mask.pb.go │ ├── structpb/ │ │ └── struct.pb.go │ ├── timestamppb/ │ │ └── timestamp.pb.go │ └── wrapperspb/ │ └── wrappers.pb.go ├── gopkg.in/ │ ├── evanphx/ │ │ └── json-patch.v4/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── README.md │ │ ├── errors.go │ │ ├── merge.go │ │ └── patch.go │ ├── inf.v0/ │ │ ├── LICENSE │ │ ├── dec.go │ │ └── rounder.go │ ├── natefinch/ │ │ ├── lumberjack.v2/ │ │ │ ├── .gitignore │ │ │ ├── .travis.yml │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── chown.go │ │ │ ├── chown_linux.go │ │ │ └── lumberjack.go │ │ └── npipe.v2/ │ │ ├── .gitignore │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── doc.go │ │ ├── npipe_windows.go │ │ ├── znpipe_windows_386.go │ │ └── znpipe_windows_amd64.go │ ├── square/ │ │ └── go-jose.v2/ │ │ ├── .gitcookies.sh.enc │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── BUG-BOUNTY.md │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── asymmetric.go │ │ ├── cipher/ │ │ │ ├── cbc_hmac.go │ │ │ ├── concat_kdf.go │ │ │ ├── ecdh_es.go │ │ │ └── key_wrap.go │ │ ├── crypter.go │ │ ├── doc.go │ │ ├── encoding.go │ │ ├── json/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── decode.go │ │ │ ├── encode.go │ │ │ ├── indent.go │ │ │ ├── scanner.go │ │ │ ├── stream.go │ │ │ └── tags.go │ │ ├── jwe.go │ │ ├── jwk.go │ │ ├── jws.go │ │ ├── jwt/ │ │ │ ├── builder.go │ │ │ ├── claims.go │ │ │ ├── doc.go │ │ │ ├── errors.go │ │ │ ├── jwt.go │ │ │ └── validation.go │ │ ├── opaque.go │ │ ├── shared.go │ │ ├── signing.go │ │ └── symmetric.go │ ├── yaml.v2/ │ │ ├── .travis.yml │ │ ├── LICENSE │ │ ├── LICENSE.libyaml │ │ ├── NOTICE │ │ ├── README.md │ │ ├── apic.go │ │ ├── decode.go │ │ ├── emitterc.go │ │ ├── encode.go │ │ ├── parserc.go │ │ ├── readerc.go │ │ ├── resolve.go │ │ ├── scannerc.go │ │ ├── sorter.go │ │ ├── writerc.go │ │ ├── yaml.go │ │ ├── yamlh.go │ │ └── yamlprivateh.go │ └── yaml.v3/ │ ├── LICENSE │ ├── NOTICE │ ├── README.md │ ├── apic.go │ ├── decode.go │ ├── emitterc.go │ ├── encode.go │ ├── parserc.go │ ├── readerc.go │ ├── resolve.go │ ├── scannerc.go │ ├── sorter.go │ ├── writerc.go │ ├── yaml.go │ ├── yamlh.go │ └── yamlprivateh.go ├── gotest.tools/ │ ├── LICENSE │ ├── assert/ │ │ ├── assert.go │ │ ├── cmp/ │ │ │ ├── compare.go │ │ │ └── result.go │ │ └── result.go │ └── internal/ │ ├── difflib/ │ │ ├── LICENSE │ │ └── difflib.go │ ├── format/ │ │ ├── diff.go │ │ └── format.go │ └── source/ │ ├── defers.go │ └── source.go ├── gvisor.dev/ │ └── gvisor/ │ ├── AUTHORS │ ├── LICENSE │ └── pkg/ │ ├── atomicbitops/ │ │ ├── 32b_32bit.go │ │ ├── 32b_64bit.go │ │ ├── aligned_32bit_unsafe.go │ │ ├── aligned_64bit.go │ │ ├── atomicbitops.go │ │ ├── atomicbitops_32bit_state_autogen.go │ │ ├── atomicbitops_32bit_unsafe_state_autogen.go │ │ ├── atomicbitops_64bit_state_autogen.go │ │ ├── atomicbitops_amd64.s │ │ ├── atomicbitops_arm64.go │ │ ├── atomicbitops_arm64.s │ │ ├── atomicbitops_arm64_state_autogen.go │ │ ├── atomicbitops_float64.go │ │ ├── atomicbitops_noasm.go │ │ └── atomicbitops_state_autogen.go │ ├── bits/ │ │ ├── bits.go │ │ ├── bits32.go │ │ ├── bits64.go │ │ ├── bits_state_autogen.go │ │ ├── uint64_arch.go │ │ ├── uint64_arch_amd64_asm.s │ │ ├── uint64_arch_arm64_asm.s │ │ └── uint64_arch_generic.go │ ├── buffer/ │ │ ├── buffer.go │ │ ├── buffer_state.go │ │ ├── buffer_state_autogen.go │ │ ├── buffer_unsafe_state_autogen.go │ │ ├── chunk.go │ │ ├── chunk_refs.go │ │ ├── view.go │ │ ├── view_list.go │ │ └── view_unsafe.go │ ├── context/ │ │ ├── context.go │ │ └── context_state_autogen.go │ ├── cpuid/ │ │ ├── cpuid.go │ │ ├── cpuid_amd64.go │ │ ├── cpuid_amd64_state_autogen.go │ │ ├── cpuid_arm64.go │ │ ├── cpuid_arm64_state_autogen.go │ │ ├── cpuid_state_autogen.go │ │ ├── features_amd64.go │ │ ├── features_arm64.go │ │ ├── native_amd64.go │ │ ├── native_amd64.s │ │ ├── native_arm64.go │ │ └── static_amd64.go │ ├── gohacks/ │ │ ├── linkname_go113_unsafe.go │ │ ├── noescape_unsafe.go │ │ ├── slice_go113_unsafe.go │ │ ├── slice_go120_unsafe.go │ │ ├── string_go113_unsafe.go │ │ └── string_go120_unsafe.go │ ├── goid/ │ │ ├── goid.go │ │ ├── goid_122_amd64.s │ │ ├── goid_122_arm64.s │ │ ├── goid_123_amd64.s │ │ └── goid_123_arm64.s │ ├── linewriter/ │ │ └── linewriter.go │ ├── log/ │ │ ├── glog.go │ │ ├── json.go │ │ ├── json_k8s.go │ │ ├── log.go │ │ └── rate_limited.go │ ├── rand/ │ │ ├── rand.go │ │ ├── rand_linux.go │ │ ├── rand_linux_state_autogen.go │ │ ├── rand_state_autogen.go │ │ └── rng.go │ ├── refs/ │ │ ├── refcounter.go │ │ ├── refs_map.go │ │ └── refs_state_autogen.go │ ├── sleep/ │ │ ├── sleep_unsafe.go │ │ └── sleep_unsafe_state_autogen.go │ ├── state/ │ │ ├── addr_range.go │ │ ├── addr_set.go │ │ ├── complete_list.go │ │ ├── decode.go │ │ ├── decode_unsafe.go │ │ ├── deferred_list.go │ │ ├── encode.go │ │ ├── encode_unsafe.go │ │ ├── state.go │ │ ├── state_norace.go │ │ ├── state_race.go │ │ ├── stats.go │ │ ├── types.go │ │ └── wire/ │ │ └── wire.go │ ├── sync/ │ │ ├── aliases.go │ │ ├── checklocks_off_unsafe.go │ │ ├── checklocks_on_unsafe.go │ │ ├── fence.go │ │ ├── fence_amd64.s │ │ ├── fence_arm64.s │ │ ├── gate_unsafe.go │ │ ├── goyield_go113_unsafe.go │ │ ├── goyield_unsafe.go │ │ ├── locking/ │ │ │ ├── atomicptrmap_ancestors_unsafe.go │ │ │ ├── atomicptrmap_goroutine_unsafe.go │ │ │ ├── lockdep.go │ │ │ ├── lockdep_norace.go │ │ │ └── locking.go │ │ ├── mutex_unsafe.go │ │ ├── nocopy.go │ │ ├── norace_unsafe.go │ │ ├── race_amd64.s │ │ ├── race_arm64.s │ │ ├── race_unsafe.go │ │ ├── runtime.go │ │ ├── runtime_amd64.go │ │ ├── runtime_constants.go │ │ ├── runtime_exectracer2.go │ │ ├── runtime_go121_unsafe.go │ │ ├── runtime_not_go121_unsafe.go │ │ ├── runtime_other.go │ │ ├── runtime_spinning_amd64.s │ │ ├── runtime_spinning_other.s │ │ ├── runtime_unsafe.go │ │ ├── rwmutex_unsafe.go │ │ ├── seqcount.go │ │ └── sync.go │ ├── tcpip/ │ │ ├── adapters/ │ │ │ └── gonet/ │ │ │ ├── gonet.go │ │ │ └── gonet_state_autogen.go │ │ ├── checksum/ │ │ │ ├── checksum.go │ │ │ ├── checksum_state_autogen.go │ │ │ ├── checksum_unsafe.go │ │ │ └── checksum_unsafe_state_autogen.go │ │ ├── errors.go │ │ ├── errors_linux.go │ │ ├── hash/ │ │ │ └── jenkins/ │ │ │ ├── jenkins.go │ │ │ └── jenkins_state_autogen.go │ │ ├── header/ │ │ │ ├── arp.go │ │ │ ├── checksum.go │ │ │ ├── datagram.go │ │ │ ├── eth.go │ │ │ ├── gue.go │ │ │ ├── header_state_autogen.go │ │ │ ├── icmpv4.go │ │ │ ├── icmpv6.go │ │ │ ├── igmp.go │ │ │ ├── igmpv3.go │ │ │ ├── interfaces.go │ │ │ ├── ipv4.go │ │ │ ├── ipv6.go │ │ │ ├── ipv6_extension_headers.go │ │ │ ├── ipv6_fragment.go │ │ │ ├── mld.go │ │ │ ├── mldv2.go │ │ │ ├── mldv2_igmpv3_common.go │ │ │ ├── ndp_neighbor_advert.go │ │ │ ├── ndp_neighbor_solicit.go │ │ │ ├── ndp_options.go │ │ │ ├── ndp_router_advert.go │ │ │ ├── ndp_router_solicit.go │ │ │ ├── ndpoptionidentifier_string.go │ │ │ ├── parse/ │ │ │ │ ├── parse.go │ │ │ │ └── parse_state_autogen.go │ │ │ ├── tcp.go │ │ │ ├── udp.go │ │ │ └── virtionet.go │ │ ├── internal/ │ │ │ └── tcp/ │ │ │ ├── tcp.go │ │ │ └── tcp_state_autogen.go │ │ ├── network/ │ │ │ ├── hash/ │ │ │ │ ├── hash.go │ │ │ │ └── hash_state_autogen.go │ │ │ ├── internal/ │ │ │ │ ├── fragmentation/ │ │ │ │ │ ├── fragmentation.go │ │ │ │ │ ├── fragmentation_state_autogen.go │ │ │ │ │ ├── reassembler.go │ │ │ │ │ └── reassembler_list.go │ │ │ │ ├── ip/ │ │ │ │ │ ├── duplicate_address_detection.go │ │ │ │ │ ├── errors.go │ │ │ │ │ ├── generic_multicast_protocol.go │ │ │ │ │ ├── ip_state_autogen.go │ │ │ │ │ └── stats.go │ │ │ │ └── multicast/ │ │ │ │ ├── multicast_state_autogen.go │ │ │ │ └── route_table.go │ │ │ ├── ipv4/ │ │ │ │ ├── icmp.go │ │ │ │ ├── igmp.go │ │ │ │ ├── ipv4.go │ │ │ │ ├── ipv4_state_autogen.go │ │ │ │ └── stats.go │ │ │ └── ipv6/ │ │ │ ├── dhcpv6configurationfromndpra_string.go │ │ │ ├── icmp.go │ │ │ ├── ipv6.go │ │ │ ├── ipv6_state_autogen.go │ │ │ ├── mld.go │ │ │ ├── ndp.go │ │ │ └── stats.go │ │ ├── ports/ │ │ │ ├── flags.go │ │ │ ├── ports.go │ │ │ └── ports_state_autogen.go │ │ ├── route_list.go │ │ ├── seqnum/ │ │ │ ├── seqnum.go │ │ │ └── seqnum_state_autogen.go │ │ ├── sock_err_list.go │ │ ├── socketops.go │ │ ├── stack/ │ │ │ ├── address_state_mutex.go │ │ │ ├── address_state_refs.go │ │ │ ├── addressable_endpoint_state.go │ │ │ ├── addressable_endpoint_state_mutex.go │ │ │ ├── bridge.go │ │ │ ├── bridge_mutex.go │ │ │ ├── bucket_mutex.go │ │ │ ├── cleanup_endpoints_mutex.go │ │ │ ├── conn_mutex.go │ │ │ ├── conn_track_mutex.go │ │ │ ├── conntrack.go │ │ │ ├── endpoints_by_nic_mutex.go │ │ │ ├── gro/ │ │ │ │ ├── gro.go │ │ │ │ ├── gro_packet_list.go │ │ │ │ └── gro_state_autogen.go │ │ │ ├── headertype_string.go │ │ │ ├── hook_string.go │ │ │ ├── icmp_rate_limit.go │ │ │ ├── iptables.go │ │ │ ├── iptables_mutex.go │ │ │ ├── iptables_targets.go │ │ │ ├── iptables_types.go │ │ │ ├── multi_port_endpoint_mutex.go │ │ │ ├── neighbor_cache.go │ │ │ ├── neighbor_cache_mutex.go │ │ │ ├── neighbor_entry.go │ │ │ ├── neighbor_entry_list.go │ │ │ ├── neighbor_entry_mutex.go │ │ │ ├── neighborstate_string.go │ │ │ ├── nic.go │ │ │ ├── nic_mutex.go │ │ │ ├── nic_stats.go │ │ │ ├── nud.go │ │ │ ├── packet_buffer.go │ │ │ ├── packet_buffer_list.go │ │ │ ├── packet_buffer_refs.go │ │ │ ├── packet_buffer_unsafe.go │ │ │ ├── packet_endpoint_list_mutex.go │ │ │ ├── packet_eps_mutex.go │ │ │ ├── packets_pending_link_resolution_mutex.go │ │ │ ├── pending_packets.go │ │ │ ├── rand.go │ │ │ ├── registration.go │ │ │ ├── route.go │ │ │ ├── route_mutex.go │ │ │ ├── route_stack_mutex.go │ │ │ ├── stack.go │ │ │ ├── stack_mutex.go │ │ │ ├── stack_options.go │ │ │ ├── stack_state_autogen.go │ │ │ ├── stack_unsafe_state_autogen.go │ │ │ ├── state_conn_mutex.go │ │ │ ├── tcp.go │ │ │ ├── transport_demuxer.go │ │ │ ├── transport_endpoints_mutex.go │ │ │ └── tuple_list.go │ │ ├── stdclock.go │ │ ├── stdclock_state.go │ │ ├── tcpip.go │ │ ├── tcpip_linux_state_autogen.go │ │ ├── tcpip_state.go │ │ ├── tcpip_state_autogen.go │ │ ├── timer.go │ │ └── transport/ │ │ ├── datagram.go │ │ ├── icmp/ │ │ │ ├── endpoint.go │ │ │ ├── endpoint_state.go │ │ │ ├── icmp_packet_list.go │ │ │ ├── icmp_state_autogen.go │ │ │ └── protocol.go │ │ ├── internal/ │ │ │ ├── network/ │ │ │ │ ├── endpoint.go │ │ │ │ ├── endpoint_state.go │ │ │ │ └── network_state_autogen.go │ │ │ └── noop/ │ │ │ ├── endpoint.go │ │ │ └── noop_state_autogen.go │ │ ├── packet/ │ │ │ ├── endpoint.go │ │ │ ├── endpoint_state.go │ │ │ ├── packet_list.go │ │ │ └── packet_state_autogen.go │ │ ├── raw/ │ │ │ ├── endpoint.go │ │ │ ├── endpoint_state.go │ │ │ ├── protocol.go │ │ │ ├── raw_packet_list.go │ │ │ └── raw_state_autogen.go │ │ ├── tcp/ │ │ │ ├── accept.go │ │ │ ├── connect.go │ │ │ ├── connect_unsafe.go │ │ │ ├── cubic.go │ │ │ ├── dispatcher.go │ │ │ ├── endpoint.go │ │ │ ├── endpoint_state.go │ │ │ ├── forwarder.go │ │ │ ├── protocol.go │ │ │ ├── rack.go │ │ │ ├── rcv.go │ │ │ ├── reno.go │ │ │ ├── reno_recovery.go │ │ │ ├── sack.go │ │ │ ├── sack_recovery.go │ │ │ ├── sack_scoreboard.go │ │ │ ├── segment.go │ │ │ ├── segment_heap.go │ │ │ ├── segment_queue.go │ │ │ ├── segment_state.go │ │ │ ├── segment_unsafe.go │ │ │ ├── snd.go │ │ │ ├── tcp_endpoint_list.go │ │ │ ├── tcp_segment_list.go │ │ │ ├── tcp_segment_refs.go │ │ │ ├── tcp_state_autogen.go │ │ │ ├── tcp_unsafe_state_autogen.go │ │ │ └── timer.go │ │ ├── tcpconntrack/ │ │ │ ├── tcp_conntrack.go │ │ │ └── tcpconntrack_state_autogen.go │ │ ├── transport.go │ │ ├── transport_state_autogen.go │ │ └── udp/ │ │ ├── endpoint.go │ │ ├── endpoint_state.go │ │ ├── forwarder.go │ │ ├── protocol.go │ │ ├── udp_packet_list.go │ │ └── udp_state_autogen.go │ └── waiter/ │ ├── waiter.go │ ├── waiter_list.go │ └── waiter_state_autogen.go ├── k8s.io/ │ ├── api/ │ │ ├── LICENSE │ │ ├── admission/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── admissionregistration/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v1alpha1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── apidiscovery/ │ │ │ ├── v2/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v2beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── apiserverinternal/ │ │ │ └── v1alpha1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ └── zz_generated.deepcopy.go │ │ ├── apps/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v1beta1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta2/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── authentication/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v1alpha1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── authorization/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── autoscaling/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v2/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v2beta1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v2beta2/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── batch/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── certificates/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v1alpha1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── coordination/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v1alpha2/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── core/ │ │ │ └── v1/ │ │ │ ├── annotation_key_constants.go │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── lifecycle.go │ │ │ ├── objectreference.go │ │ │ ├── register.go │ │ │ ├── resource.go │ │ │ ├── taint.go │ │ │ ├── toleration.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── well_known_labels.go │ │ │ ├── well_known_taints.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── discovery/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── well_known_labels.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── well_known_labels.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── events/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── extensions/ │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── flowcontrol/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v1beta1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v1beta2/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta3/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── imagepolicy/ │ │ │ └── v1alpha1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ └── zz_generated.deepcopy.go │ │ ├── networking/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── well_known_annotations.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v1alpha1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── well_known_labels.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── well_known_annotations.go │ │ │ ├── well_known_labels.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── node/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v1alpha1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ └── zz_generated.deepcopy.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── policy/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── rbac/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v1alpha1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ └── zz_generated.deepcopy.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── resource/ │ │ │ ├── v1alpha3/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── scheduling/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v1alpha1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ └── zz_generated.deepcopy.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ ├── storage/ │ │ │ ├── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v1alpha1/ │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_swagger_doc_generated.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ └── storagemigration/ │ │ └── v1alpha1/ │ │ ├── doc.go │ │ ├── generated.pb.go │ │ ├── generated.proto │ │ ├── register.go │ │ ├── types.go │ │ ├── types_swagger_doc_generated.go │ │ ├── zz_generated.deepcopy.go │ │ └── zz_generated.prerelease-lifecycle.go │ ├── apiextensions-apiserver/ │ │ ├── LICENSE │ │ └── pkg/ │ │ └── apis/ │ │ └── apiextensions/ │ │ ├── deepcopy.go │ │ ├── doc.go │ │ ├── helpers.go │ │ ├── register.go │ │ ├── types.go │ │ ├── types_jsonschema.go │ │ ├── v1/ │ │ │ ├── .import-restrictions │ │ │ ├── conversion.go │ │ │ ├── deepcopy.go │ │ │ ├── defaults.go │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── marshal.go │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── types_jsonschema.go │ │ │ ├── zz_generated.conversion.go │ │ │ ├── zz_generated.deepcopy.go │ │ │ ├── zz_generated.defaults.go │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ └── zz_generated.deepcopy.go │ ├── apimachinery/ │ │ ├── LICENSE │ │ ├── pkg/ │ │ │ ├── api/ │ │ │ │ ├── equality/ │ │ │ │ │ └── semantic.go │ │ │ │ ├── errors/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── doc.go │ │ │ │ │ └── errors.go │ │ │ │ ├── meta/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── conditions.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── errors.go │ │ │ │ │ ├── firsthit_restmapper.go │ │ │ │ │ ├── help.go │ │ │ │ │ ├── interfaces.go │ │ │ │ │ ├── lazy.go │ │ │ │ │ ├── meta.go │ │ │ │ │ ├── multirestmapper.go │ │ │ │ │ ├── priority.go │ │ │ │ │ ├── restmapper.go │ │ │ │ │ └── testrestmapper/ │ │ │ │ │ └── test_restmapper.go │ │ │ │ ├── resource/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── amount.go │ │ │ │ │ ├── generated.pb.go │ │ │ │ │ ├── generated.proto │ │ │ │ │ ├── math.go │ │ │ │ │ ├── quantity.go │ │ │ │ │ ├── quantity_proto.go │ │ │ │ │ ├── scale_int.go │ │ │ │ │ ├── suffix.go │ │ │ │ │ └── zz_generated.deepcopy.go │ │ │ │ └── validation/ │ │ │ │ ├── OWNERS │ │ │ │ ├── doc.go │ │ │ │ ├── generic.go │ │ │ │ ├── objectmeta.go │ │ │ │ └── path/ │ │ │ │ └── name.go │ │ │ ├── apis/ │ │ │ │ └── meta/ │ │ │ │ ├── internalversion/ │ │ │ │ │ ├── defaults.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── register.go │ │ │ │ │ ├── scheme/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ └── register.go │ │ │ │ │ ├── types.go │ │ │ │ │ ├── validation/ │ │ │ │ │ │ └── validation.go │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ └── zz_generated.deepcopy.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── controller_ref.go │ │ │ │ │ ├── conversion.go │ │ │ │ │ ├── deepcopy.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── duration.go │ │ │ │ │ ├── generated.pb.go │ │ │ │ │ ├── generated.proto │ │ │ │ │ ├── group_version.go │ │ │ │ │ ├── helpers.go │ │ │ │ │ ├── labels.go │ │ │ │ │ ├── meta.go │ │ │ │ │ ├── micro_time.go │ │ │ │ │ ├── micro_time_fuzz.go │ │ │ │ │ ├── micro_time_proto.go │ │ │ │ │ ├── register.go │ │ │ │ │ ├── time.go │ │ │ │ │ ├── time_fuzz.go │ │ │ │ │ ├── time_proto.go │ │ │ │ │ ├── types.go │ │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ │ ├── unstructured/ │ │ │ │ │ │ ├── helpers.go │ │ │ │ │ │ ├── unstructured.go │ │ │ │ │ │ ├── unstructured_list.go │ │ │ │ │ │ └── zz_generated.deepcopy.go │ │ │ │ │ ├── validation/ │ │ │ │ │ │ └── validation.go │ │ │ │ │ ├── watch.go │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── conversion.go │ │ │ │ ├── deepcopy.go │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── types_swagger_doc_generated.go │ │ │ │ ├── validation/ │ │ │ │ │ └── validation.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ └── zz_generated.defaults.go │ │ │ ├── conversion/ │ │ │ │ ├── converter.go │ │ │ │ ├── deep_equal.go │ │ │ │ ├── doc.go │ │ │ │ ├── helper.go │ │ │ │ └── queryparams/ │ │ │ │ ├── convert.go │ │ │ │ └── doc.go │ │ │ ├── fields/ │ │ │ │ ├── doc.go │ │ │ │ ├── fields.go │ │ │ │ ├── requirements.go │ │ │ │ └── selector.go │ │ │ ├── labels/ │ │ │ │ ├── doc.go │ │ │ │ ├── labels.go │ │ │ │ ├── selector.go │ │ │ │ └── zz_generated.deepcopy.go │ │ │ ├── runtime/ │ │ │ │ ├── allocator.go │ │ │ │ ├── codec.go │ │ │ │ ├── codec_check.go │ │ │ │ ├── conversion.go │ │ │ │ ├── converter.go │ │ │ │ ├── doc.go │ │ │ │ ├── embedded.go │ │ │ │ ├── error.go │ │ │ │ ├── extension.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── helper.go │ │ │ │ ├── interfaces.go │ │ │ │ ├── mapper.go │ │ │ │ ├── negotiate.go │ │ │ │ ├── register.go │ │ │ │ ├── schema/ │ │ │ │ │ ├── generated.pb.go │ │ │ │ │ ├── generated.proto │ │ │ │ │ ├── group_version.go │ │ │ │ │ └── interfaces.go │ │ │ │ ├── scheme.go │ │ │ │ ├── scheme_builder.go │ │ │ │ ├── serializer/ │ │ │ │ │ ├── cbor/ │ │ │ │ │ │ ├── cbor.go │ │ │ │ │ │ ├── direct/ │ │ │ │ │ │ │ └── direct.go │ │ │ │ │ │ ├── framer.go │ │ │ │ │ │ ├── internal/ │ │ │ │ │ │ │ └── modes/ │ │ │ │ │ │ │ ├── buffers.go │ │ │ │ │ │ │ ├── custom.go │ │ │ │ │ │ │ ├── decode.go │ │ │ │ │ │ │ ├── diagnostic.go │ │ │ │ │ │ │ └── encode.go │ │ │ │ │ │ └── raw.go │ │ │ │ │ ├── codec_factory.go │ │ │ │ │ ├── json/ │ │ │ │ │ │ ├── json.go │ │ │ │ │ │ └── meta.go │ │ │ │ │ ├── negotiated_codec.go │ │ │ │ │ ├── protobuf/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ └── protobuf.go │ │ │ │ │ ├── recognizer/ │ │ │ │ │ │ └── recognizer.go │ │ │ │ │ ├── streaming/ │ │ │ │ │ │ └── streaming.go │ │ │ │ │ └── versioning/ │ │ │ │ │ └── versioning.go │ │ │ │ ├── splice.go │ │ │ │ ├── swagger_doc_generator.go │ │ │ │ ├── types.go │ │ │ │ ├── types_proto.go │ │ │ │ └── zz_generated.deepcopy.go │ │ │ ├── selection/ │ │ │ │ └── operator.go │ │ │ ├── types/ │ │ │ │ ├── doc.go │ │ │ │ ├── namespacedname.go │ │ │ │ ├── nodename.go │ │ │ │ ├── patch.go │ │ │ │ └── uid.go │ │ │ ├── util/ │ │ │ │ ├── cache/ │ │ │ │ │ ├── expiring.go │ │ │ │ │ └── lruexpirecache.go │ │ │ │ ├── diff/ │ │ │ │ │ └── diff.go │ │ │ │ ├── dump/ │ │ │ │ │ └── dump.go │ │ │ │ ├── duration/ │ │ │ │ │ └── duration.go │ │ │ │ ├── errors/ │ │ │ │ │ ├── doc.go │ │ │ │ │ └── errors.go │ │ │ │ ├── framer/ │ │ │ │ │ └── framer.go │ │ │ │ ├── httpstream/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── httpstream.go │ │ │ │ │ ├── spdy/ │ │ │ │ │ │ ├── connection.go │ │ │ │ │ │ ├── roundtripper.go │ │ │ │ │ │ └── upgrade.go │ │ │ │ │ └── wsstream/ │ │ │ │ │ ├── conn.go │ │ │ │ │ ├── doc.go │ │ │ │ │ └── stream.go │ │ │ │ ├── intstr/ │ │ │ │ │ ├── generated.pb.go │ │ │ │ │ ├── generated.proto │ │ │ │ │ ├── instr_fuzz.go │ │ │ │ │ └── intstr.go │ │ │ │ ├── json/ │ │ │ │ │ └── json.go │ │ │ │ ├── managedfields/ │ │ │ │ │ ├── endpoints.yaml │ │ │ │ │ ├── extract.go │ │ │ │ │ ├── fieldmanager.go │ │ │ │ │ ├── gvkparser.go │ │ │ │ │ ├── internal/ │ │ │ │ │ │ ├── atmostevery.go │ │ │ │ │ │ ├── buildmanagerinfo.go │ │ │ │ │ │ ├── capmanagers.go │ │ │ │ │ │ ├── conflict.go │ │ │ │ │ │ ├── fieldmanager.go │ │ │ │ │ │ ├── fields.go │ │ │ │ │ │ ├── lastapplied.go │ │ │ │ │ │ ├── lastappliedmanager.go │ │ │ │ │ │ ├── lastappliedupdater.go │ │ │ │ │ │ ├── managedfields.go │ │ │ │ │ │ ├── managedfieldsupdater.go │ │ │ │ │ │ ├── manager.go │ │ │ │ │ │ ├── pathelement.go │ │ │ │ │ │ ├── skipnonapplied.go │ │ │ │ │ │ ├── stripmeta.go │ │ │ │ │ │ ├── structuredmerge.go │ │ │ │ │ │ ├── typeconverter.go │ │ │ │ │ │ ├── versioncheck.go │ │ │ │ │ │ └── versionconverter.go │ │ │ │ │ ├── node.yaml │ │ │ │ │ ├── pod.yaml │ │ │ │ │ ├── scalehandler.go │ │ │ │ │ └── typeconverter.go │ │ │ │ ├── mergepatch/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── errors.go │ │ │ │ │ └── util.go │ │ │ │ ├── naming/ │ │ │ │ │ └── from_stack.go │ │ │ │ ├── net/ │ │ │ │ │ ├── http.go │ │ │ │ │ ├── interface.go │ │ │ │ │ ├── port_range.go │ │ │ │ │ ├── port_split.go │ │ │ │ │ └── util.go │ │ │ │ ├── portforward/ │ │ │ │ │ └── constants.go │ │ │ │ ├── proxy/ │ │ │ │ │ ├── dial.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── transport.go │ │ │ │ │ └── upgradeaware.go │ │ │ │ ├── rand/ │ │ │ │ │ └── rand.go │ │ │ │ ├── remotecommand/ │ │ │ │ │ └── constants.go │ │ │ │ ├── runtime/ │ │ │ │ │ └── runtime.go │ │ │ │ ├── sets/ │ │ │ │ │ ├── byte.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── empty.go │ │ │ │ │ ├── int.go │ │ │ │ │ ├── int32.go │ │ │ │ │ ├── int64.go │ │ │ │ │ ├── set.go │ │ │ │ │ └── string.go │ │ │ │ ├── strategicpatch/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── errors.go │ │ │ │ │ ├── meta.go │ │ │ │ │ ├── patch.go │ │ │ │ │ └── types.go │ │ │ │ ├── uuid/ │ │ │ │ │ └── uuid.go │ │ │ │ ├── validation/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── field/ │ │ │ │ │ │ ├── errors.go │ │ │ │ │ │ └── path.go │ │ │ │ │ └── validation.go │ │ │ │ ├── version/ │ │ │ │ │ ├── doc.go │ │ │ │ │ └── version.go │ │ │ │ ├── wait/ │ │ │ │ │ ├── backoff.go │ │ │ │ │ ├── delay.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── error.go │ │ │ │ │ ├── loop.go │ │ │ │ │ ├── poll.go │ │ │ │ │ ├── timer.go │ │ │ │ │ └── wait.go │ │ │ │ ├── waitgroup/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── ratelimited_waitgroup.go │ │ │ │ │ └── waitgroup.go │ │ │ │ └── yaml/ │ │ │ │ └── decoder.go │ │ │ ├── version/ │ │ │ │ ├── doc.go │ │ │ │ ├── helpers.go │ │ │ │ └── types.go │ │ │ └── watch/ │ │ │ ├── doc.go │ │ │ ├── filter.go │ │ │ ├── mux.go │ │ │ ├── streamwatcher.go │ │ │ ├── watch.go │ │ │ └── zz_generated.deepcopy.go │ │ └── third_party/ │ │ └── forked/ │ │ └── golang/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── json/ │ │ │ ├── OWNERS │ │ │ └── fields.go │ │ ├── netutil/ │ │ │ └── addr.go │ │ └── reflect/ │ │ └── deep_equal.go │ ├── apiserver/ │ │ ├── LICENSE │ │ ├── pkg/ │ │ │ ├── admission/ │ │ │ │ ├── attributes.go │ │ │ │ ├── audit.go │ │ │ │ ├── chain.go │ │ │ │ ├── config.go │ │ │ │ ├── configuration/ │ │ │ │ │ ├── configuration_manager.go │ │ │ │ │ ├── mutating_webhook_manager.go │ │ │ │ │ └── validating_webhook_manager.go │ │ │ │ ├── conversion.go │ │ │ │ ├── decorator.go │ │ │ │ ├── errors.go │ │ │ │ ├── handler.go │ │ │ │ ├── initializer/ │ │ │ │ │ ├── initializer.go │ │ │ │ │ └── interfaces.go │ │ │ │ ├── interfaces.go │ │ │ │ ├── metrics/ │ │ │ │ │ └── metrics.go │ │ │ │ ├── plugin/ │ │ │ │ │ ├── authorizer/ │ │ │ │ │ │ └── caching_authorizer.go │ │ │ │ │ ├── cel/ │ │ │ │ │ │ ├── OWNERS │ │ │ │ │ │ ├── activation.go │ │ │ │ │ │ ├── compile.go │ │ │ │ │ │ ├── composition.go │ │ │ │ │ │ ├── condition.go │ │ │ │ │ │ ├── interface.go │ │ │ │ │ │ └── mutation.go │ │ │ │ │ ├── namespace/ │ │ │ │ │ │ └── lifecycle/ │ │ │ │ │ │ └── admission.go │ │ │ │ │ ├── policy/ │ │ │ │ │ │ ├── generic/ │ │ │ │ │ │ │ ├── accessor.go │ │ │ │ │ │ │ ├── interfaces.go │ │ │ │ │ │ │ ├── plugin.go │ │ │ │ │ │ │ ├── policy_dispatcher.go │ │ │ │ │ │ │ ├── policy_matcher.go │ │ │ │ │ │ │ ├── policy_source.go │ │ │ │ │ │ │ └── policy_test_context.go │ │ │ │ │ │ ├── internal/ │ │ │ │ │ │ │ └── generic/ │ │ │ │ │ │ │ ├── controller.go │ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ │ ├── informer.go │ │ │ │ │ │ │ ├── interface.go │ │ │ │ │ │ │ └── lister.go │ │ │ │ │ │ ├── matching/ │ │ │ │ │ │ │ └── matching.go │ │ │ │ │ │ ├── mutating/ │ │ │ │ │ │ │ ├── accessor.go │ │ │ │ │ │ │ ├── compilation.go │ │ │ │ │ │ │ ├── dispatcher.go │ │ │ │ │ │ │ ├── patch/ │ │ │ │ │ │ │ │ ├── interface.go │ │ │ │ │ │ │ │ ├── json_patch.go │ │ │ │ │ │ │ │ ├── smd.go │ │ │ │ │ │ │ │ └── typeconverter.go │ │ │ │ │ │ │ ├── plugin.go │ │ │ │ │ │ │ └── reinvocationcontext.go │ │ │ │ │ │ └── validating/ │ │ │ │ │ │ ├── accessor.go │ │ │ │ │ │ ├── dispatcher.go │ │ │ │ │ │ ├── errors.go │ │ │ │ │ │ ├── initializer.go │ │ │ │ │ │ ├── interface.go │ │ │ │ │ │ ├── message.go │ │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ │ ├── errors.go │ │ │ │ │ │ │ └── metrics.go │ │ │ │ │ │ ├── plugin.go │ │ │ │ │ │ ├── policy_decision.go │ │ │ │ │ │ ├── typechecking.go │ │ │ │ │ │ └── validator.go │ │ │ │ │ └── webhook/ │ │ │ │ │ ├── accessors.go │ │ │ │ │ ├── config/ │ │ │ │ │ │ ├── apis/ │ │ │ │ │ │ │ └── webhookadmission/ │ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ │ ├── register.go │ │ │ │ │ │ │ ├── types.go │ │ │ │ │ │ │ ├── v1/ │ │ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ │ │ ├── register.go │ │ │ │ │ │ │ │ ├── types.go │ │ │ │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ │ │ │ ├── v1alpha1/ │ │ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ │ │ ├── register.go │ │ │ │ │ │ │ │ ├── types.go │ │ │ │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ │ │ │ └── zz_generated.deepcopy.go │ │ │ │ │ │ └── kubeconfig.go │ │ │ │ │ ├── errors/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ └── statuserror.go │ │ │ │ │ ├── generic/ │ │ │ │ │ │ ├── interfaces.go │ │ │ │ │ │ └── webhook.go │ │ │ │ │ ├── matchconditions/ │ │ │ │ │ │ ├── interface.go │ │ │ │ │ │ └── matcher.go │ │ │ │ │ ├── mutating/ │ │ │ │ │ │ ├── dispatcher.go │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── plugin.go │ │ │ │ │ │ └── reinvocationcontext.go │ │ │ │ │ ├── predicates/ │ │ │ │ │ │ ├── namespace/ │ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ │ └── matcher.go │ │ │ │ │ │ ├── object/ │ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ │ └── matcher.go │ │ │ │ │ │ └── rules/ │ │ │ │ │ │ └── rules.go │ │ │ │ │ ├── request/ │ │ │ │ │ │ ├── admissionreview.go │ │ │ │ │ │ └── doc.go │ │ │ │ │ └── validating/ │ │ │ │ │ ├── dispatcher.go │ │ │ │ │ ├── doc.go │ │ │ │ │ └── plugin.go │ │ │ │ ├── plugins.go │ │ │ │ ├── reinvocation.go │ │ │ │ └── util.go │ │ │ ├── apis/ │ │ │ │ ├── apidiscovery/ │ │ │ │ │ └── v2/ │ │ │ │ │ ├── conversion.go │ │ │ │ │ ├── doc.go │ │ │ │ │ └── register.go │ │ │ │ ├── apiserver/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── install/ │ │ │ │ │ │ └── install.go │ │ │ │ │ ├── register.go │ │ │ │ │ ├── types.go │ │ │ │ │ ├── types_encryption.go │ │ │ │ │ ├── v1/ │ │ │ │ │ │ ├── defaults.go │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── register.go │ │ │ │ │ │ ├── types.go │ │ │ │ │ │ ├── types_encryption.go │ │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ │ ├── v1alpha1/ │ │ │ │ │ │ ├── conversion.go │ │ │ │ │ │ ├── defaults.go │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── register.go │ │ │ │ │ │ ├── types.go │ │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ │ ├── v1beta1/ │ │ │ │ │ │ ├── conversion.go │ │ │ │ │ │ ├── defaults.go │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── register.go │ │ │ │ │ │ ├── types.go │ │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ │ └── zz_generated.deepcopy.go │ │ │ │ ├── audit/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── helpers.go │ │ │ │ │ ├── register.go │ │ │ │ │ ├── types.go │ │ │ │ │ ├── v1/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── generated.pb.go │ │ │ │ │ │ ├── generated.proto │ │ │ │ │ │ ├── register.go │ │ │ │ │ │ ├── types.go │ │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ │ └── zz_generated.deepcopy.go │ │ │ │ ├── cel/ │ │ │ │ │ └── config.go │ │ │ │ └── flowcontrol/ │ │ │ │ └── bootstrap/ │ │ │ │ └── default.go │ │ │ ├── audit/ │ │ │ │ ├── OWNERS │ │ │ │ ├── context.go │ │ │ │ ├── evaluator.go │ │ │ │ ├── format.go │ │ │ │ ├── metrics.go │ │ │ │ ├── request.go │ │ │ │ ├── scheme.go │ │ │ │ ├── types.go │ │ │ │ └── union.go │ │ │ ├── authentication/ │ │ │ │ ├── authenticator/ │ │ │ │ │ ├── audagnostic.go │ │ │ │ │ ├── audiences.go │ │ │ │ │ └── interfaces.go │ │ │ │ ├── authenticatorfactory/ │ │ │ │ │ ├── delegating.go │ │ │ │ │ ├── loopback.go │ │ │ │ │ ├── metrics.go │ │ │ │ │ └── requestheader.go │ │ │ │ ├── group/ │ │ │ │ │ ├── authenticated_group_adder.go │ │ │ │ │ ├── group_adder.go │ │ │ │ │ └── token_group_adder.go │ │ │ │ ├── request/ │ │ │ │ │ ├── anonymous/ │ │ │ │ │ │ └── anonymous.go │ │ │ │ │ ├── bearertoken/ │ │ │ │ │ │ └── bearertoken.go │ │ │ │ │ ├── headerrequest/ │ │ │ │ │ │ ├── requestheader.go │ │ │ │ │ │ └── requestheader_controller.go │ │ │ │ │ ├── union/ │ │ │ │ │ │ └── union.go │ │ │ │ │ ├── websocket/ │ │ │ │ │ │ └── protocol.go │ │ │ │ │ └── x509/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── verify_options.go │ │ │ │ │ └── x509.go │ │ │ │ ├── serviceaccount/ │ │ │ │ │ └── util.go │ │ │ │ ├── token/ │ │ │ │ │ ├── cache/ │ │ │ │ │ │ ├── cache_simple.go │ │ │ │ │ │ ├── cache_striped.go │ │ │ │ │ │ ├── cached_token_authenticator.go │ │ │ │ │ │ └── stats.go │ │ │ │ │ └── tokenfile/ │ │ │ │ │ └── tokenfile.go │ │ │ │ └── user/ │ │ │ │ ├── doc.go │ │ │ │ └── user.go │ │ │ ├── authorization/ │ │ │ │ └── authorizer/ │ │ │ │ ├── interfaces.go │ │ │ │ └── rule.go │ │ │ ├── cel/ │ │ │ │ ├── OWNERS │ │ │ │ ├── cidr.go │ │ │ │ ├── common/ │ │ │ │ │ ├── adaptor.go │ │ │ │ │ ├── equality.go │ │ │ │ │ ├── maplist.go │ │ │ │ │ ├── schemas.go │ │ │ │ │ ├── typeprovider.go │ │ │ │ │ └── values.go │ │ │ │ ├── environment/ │ │ │ │ │ ├── base.go │ │ │ │ │ └── environment.go │ │ │ │ ├── errors.go │ │ │ │ ├── escaping.go │ │ │ │ ├── format.go │ │ │ │ ├── ip.go │ │ │ │ ├── lazy/ │ │ │ │ │ └── lazy.go │ │ │ │ ├── library/ │ │ │ │ │ ├── authz.go │ │ │ │ │ ├── cidr.go │ │ │ │ │ ├── cost.go │ │ │ │ │ ├── format.go │ │ │ │ │ ├── ip.go │ │ │ │ │ ├── jsonpatch.go │ │ │ │ │ ├── libraries.go │ │ │ │ │ ├── lists.go │ │ │ │ │ ├── quantity.go │ │ │ │ │ ├── regex.go │ │ │ │ │ ├── semverlib.go │ │ │ │ │ ├── test.go │ │ │ │ │ └── urls.go │ │ │ │ ├── limits.go │ │ │ │ ├── mutation/ │ │ │ │ │ ├── dynamic/ │ │ │ │ │ │ └── objects.go │ │ │ │ │ ├── jsonpatch.go │ │ │ │ │ └── typeresolver.go │ │ │ │ ├── openapi/ │ │ │ │ │ ├── adaptor.go │ │ │ │ │ ├── extensions.go │ │ │ │ │ └── resolver/ │ │ │ │ │ ├── combined.go │ │ │ │ │ ├── definitions.go │ │ │ │ │ ├── discovery.go │ │ │ │ │ ├── refs.go │ │ │ │ │ └── resolver.go │ │ │ │ ├── quantity.go │ │ │ │ ├── semver.go │ │ │ │ ├── types.go │ │ │ │ ├── url.go │ │ │ │ └── value.go │ │ │ ├── endpoints/ │ │ │ │ ├── OWNERS │ │ │ │ ├── deprecation/ │ │ │ │ │ └── deprecation.go │ │ │ │ ├── discovery/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── addresses.go │ │ │ │ │ ├── aggregated/ │ │ │ │ │ │ ├── etag.go │ │ │ │ │ │ ├── fake.go │ │ │ │ │ │ ├── handler.go │ │ │ │ │ │ ├── metrics.go │ │ │ │ │ │ ├── negotiation.go │ │ │ │ │ │ └── wrapper.go │ │ │ │ │ ├── group.go │ │ │ │ │ ├── legacy.go │ │ │ │ │ ├── root.go │ │ │ │ │ ├── storageversionhash.go │ │ │ │ │ ├── util.go │ │ │ │ │ └── version.go │ │ │ │ ├── doc.go │ │ │ │ ├── filterlatency/ │ │ │ │ │ └── filterlatency.go │ │ │ │ ├── filters/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── audit.go │ │ │ │ │ ├── audit_init.go │ │ │ │ │ ├── authentication.go │ │ │ │ │ ├── authn_audit.go │ │ │ │ │ ├── authorization.go │ │ │ │ │ ├── cachecontrol.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── impersonation.go │ │ │ │ │ ├── metrics.go │ │ │ │ │ ├── mux_discovery_complete.go │ │ │ │ │ ├── request_deadline.go │ │ │ │ │ ├── request_received_time.go │ │ │ │ │ ├── requestinfo.go │ │ │ │ │ ├── storageversion.go │ │ │ │ │ ├── traces.go │ │ │ │ │ ├── warning.go │ │ │ │ │ └── webhook_duration.go │ │ │ │ ├── groupversion.go │ │ │ │ ├── handlers/ │ │ │ │ │ ├── create.go │ │ │ │ │ ├── delete.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fieldmanager/ │ │ │ │ │ │ ├── OWNERS │ │ │ │ │ │ ├── admission.go │ │ │ │ │ │ ├── endpoints.yaml │ │ │ │ │ │ ├── equality.go │ │ │ │ │ │ ├── node.yaml │ │ │ │ │ │ └── pod.yaml │ │ │ │ │ ├── finisher/ │ │ │ │ │ │ └── finisher.go │ │ │ │ │ ├── get.go │ │ │ │ │ ├── helpers.go │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── OWNERS │ │ │ │ │ │ └── metrics.go │ │ │ │ │ ├── namer.go │ │ │ │ │ ├── negotiation/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── errors.go │ │ │ │ │ │ └── negotiate.go │ │ │ │ │ ├── patch.go │ │ │ │ │ ├── response.go │ │ │ │ │ ├── responsewriters/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── errors.go │ │ │ │ │ │ ├── status.go │ │ │ │ │ │ └── writers.go │ │ │ │ │ ├── rest.go │ │ │ │ │ ├── trace_util.go │ │ │ │ │ ├── update.go │ │ │ │ │ └── watch.go │ │ │ │ ├── installer.go │ │ │ │ ├── metrics/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ └── metrics.go │ │ │ │ ├── openapi/ │ │ │ │ │ └── openapi.go │ │ │ │ ├── request/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── context.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── received_time.go │ │ │ │ │ ├── requestinfo.go │ │ │ │ │ ├── server_shutdown_signal.go │ │ │ │ │ └── webhook_duration.go │ │ │ │ ├── responsewriter/ │ │ │ │ │ ├── fake.go │ │ │ │ │ └── wrapper.go │ │ │ │ └── warning/ │ │ │ │ └── warning.go │ │ │ ├── features/ │ │ │ │ ├── OWNERS │ │ │ │ └── kube_features.go │ │ │ ├── quota/ │ │ │ │ └── v1/ │ │ │ │ ├── OWNERS │ │ │ │ ├── interfaces.go │ │ │ │ └── resources.go │ │ │ ├── registry/ │ │ │ │ ├── generic/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── matcher.go │ │ │ │ │ ├── options.go │ │ │ │ │ ├── registry/ │ │ │ │ │ │ ├── corrupt_obj_deleter.go │ │ │ │ │ │ ├── decorated_watcher.go │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── dryrun.go │ │ │ │ │ │ ├── storage_factory.go │ │ │ │ │ │ └── store.go │ │ │ │ │ └── storage_decorator.go │ │ │ │ └── rest/ │ │ │ │ ├── OWNERS │ │ │ │ ├── create.go │ │ │ │ ├── create_update.go │ │ │ │ ├── delete.go │ │ │ │ ├── doc.go │ │ │ │ ├── meta.go │ │ │ │ ├── rest.go │ │ │ │ ├── table.go │ │ │ │ └── update.go │ │ │ ├── server/ │ │ │ │ ├── config.go │ │ │ │ ├── config_selfclient.go │ │ │ │ ├── deleted_kinds.go │ │ │ │ ├── deprecated_insecure_serving.go │ │ │ │ ├── doc.go │ │ │ │ ├── dynamiccertificates/ │ │ │ │ │ ├── cert_key.go │ │ │ │ │ ├── client_ca.go │ │ │ │ │ ├── configmap_cafile_content.go │ │ │ │ │ ├── dynamic_cafile_content.go │ │ │ │ │ ├── dynamic_serving_content.go │ │ │ │ │ ├── dynamic_sni_content.go │ │ │ │ │ ├── interfaces.go │ │ │ │ │ ├── named_certificates.go │ │ │ │ │ ├── static_content.go │ │ │ │ │ ├── tlsconfig.go │ │ │ │ │ ├── union_content.go │ │ │ │ │ └── util.go │ │ │ │ ├── egressselector/ │ │ │ │ │ ├── config.go │ │ │ │ │ ├── egress_selector.go │ │ │ │ │ └── metrics/ │ │ │ │ │ └── metrics.go │ │ │ │ ├── filters/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── content_type.go │ │ │ │ │ ├── cors.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── goaway.go │ │ │ │ │ ├── hsts.go │ │ │ │ │ ├── longrunning.go │ │ │ │ │ ├── maxinflight.go │ │ │ │ │ ├── priority-and-fairness.go │ │ │ │ │ ├── timeout.go │ │ │ │ │ ├── waitgroup.go │ │ │ │ │ ├── watch_termination.go │ │ │ │ │ ├── with_retry_after.go │ │ │ │ │ └── wrap.go │ │ │ │ ├── genericapiserver.go │ │ │ │ ├── handler.go │ │ │ │ ├── healthz/ │ │ │ │ │ ├── doc.go │ │ │ │ │ └── healthz.go │ │ │ │ ├── healthz.go │ │ │ │ ├── hooks.go │ │ │ │ ├── httplog/ │ │ │ │ │ ├── doc.go │ │ │ │ │ └── httplog.go │ │ │ │ ├── lifecycle_signals.go │ │ │ │ ├── mux/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── doc.go │ │ │ │ │ └── pathrecorder.go │ │ │ │ ├── plugins.go │ │ │ │ ├── routes/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── debugsocket.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── flags.go │ │ │ │ │ ├── index.go │ │ │ │ │ ├── metrics.go │ │ │ │ │ ├── openapi.go │ │ │ │ │ ├── profiling.go │ │ │ │ │ └── version.go │ │ │ │ ├── routine/ │ │ │ │ │ └── routine.go │ │ │ │ ├── secure_serving.go │ │ │ │ ├── signal.go │ │ │ │ ├── signal_posix.go │ │ │ │ ├── signal_windows.go │ │ │ │ ├── storage/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── resource_config.go │ │ │ │ │ ├── resource_encoding_config.go │ │ │ │ │ ├── storage_codec.go │ │ │ │ │ └── storage_factory.go │ │ │ │ └── storage_readiness_hook.go │ │ │ ├── storage/ │ │ │ │ ├── OWNERS │ │ │ │ ├── api_object_versioner.go │ │ │ │ ├── cacher/ │ │ │ │ │ ├── cache_watcher.go │ │ │ │ │ ├── cacher.go │ │ │ │ │ ├── caching_object.go │ │ │ │ │ ├── lister_watcher.go │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── OWNERS │ │ │ │ │ │ └── metrics.go │ │ │ │ │ ├── ready.go │ │ │ │ │ ├── store.go │ │ │ │ │ ├── store_btree.go │ │ │ │ │ ├── time_budget.go │ │ │ │ │ ├── util.go │ │ │ │ │ ├── watch_cache.go │ │ │ │ │ ├── watch_cache_interval.go │ │ │ │ │ └── watch_progress.go │ │ │ │ ├── continue.go │ │ │ │ ├── doc.go │ │ │ │ ├── errors/ │ │ │ │ │ ├── doc.go │ │ │ │ │ └── storage.go │ │ │ │ ├── errors.go │ │ │ │ ├── etcd3/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── compact.go │ │ │ │ │ ├── corrupt_obj_deleter.go │ │ │ │ │ ├── decoder.go │ │ │ │ │ ├── errors.go │ │ │ │ │ ├── event.go │ │ │ │ │ ├── healthcheck.go │ │ │ │ │ ├── latency_tracker.go │ │ │ │ │ ├── lease_manager.go │ │ │ │ │ ├── logger.go │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── OWNERS │ │ │ │ │ │ └── metrics.go │ │ │ │ │ ├── store.go │ │ │ │ │ └── watcher.go │ │ │ │ ├── feature/ │ │ │ │ │ └── feature_support_checker.go │ │ │ │ ├── interfaces.go │ │ │ │ ├── names/ │ │ │ │ │ └── generate.go │ │ │ │ ├── selection_predicate.go │ │ │ │ ├── storagebackend/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── config.go │ │ │ │ │ └── factory/ │ │ │ │ │ ├── etcd3.go │ │ │ │ │ └── factory.go │ │ │ │ ├── util.go │ │ │ │ └── value/ │ │ │ │ ├── OWNERS │ │ │ │ ├── encrypt/ │ │ │ │ │ └── identity/ │ │ │ │ │ └── identity.go │ │ │ │ ├── metrics.go │ │ │ │ └── transformer.go │ │ │ ├── storageversion/ │ │ │ │ ├── OWNERS │ │ │ │ ├── manager.go │ │ │ │ └── updater.go │ │ │ ├── util/ │ │ │ │ ├── apihelpers/ │ │ │ │ │ └── helpers.go │ │ │ │ ├── dryrun/ │ │ │ │ │ └── dryrun.go │ │ │ │ ├── feature/ │ │ │ │ │ └── feature_gate.go │ │ │ │ ├── flowcontrol/ │ │ │ │ │ ├── OWNERS │ │ │ │ │ ├── apf_context.go │ │ │ │ │ ├── apf_controller.go │ │ │ │ │ ├── apf_controller_debug.go │ │ │ │ │ ├── apf_filter.go │ │ │ │ │ ├── conc_alloc.go │ │ │ │ │ ├── debug/ │ │ │ │ │ │ └── dump.go │ │ │ │ │ ├── dropped_requests_tracker.go │ │ │ │ │ ├── fairqueuing/ │ │ │ │ │ │ ├── eventclock/ │ │ │ │ │ │ │ ├── interface.go │ │ │ │ │ │ │ └── real.go │ │ │ │ │ │ ├── integrator.go │ │ │ │ │ │ ├── interface.go │ │ │ │ │ │ ├── promise/ │ │ │ │ │ │ │ ├── interface.go │ │ │ │ │ │ │ └── promise.go │ │ │ │ │ │ └── queueset/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fifo_list.go │ │ │ │ │ │ ├── queueset.go │ │ │ │ │ │ └── types.go │ │ │ │ │ ├── format/ │ │ │ │ │ │ └── formatting.go │ │ │ │ │ ├── formatting.go │ │ │ │ │ ├── max_seats.go │ │ │ │ │ ├── metrics/ │ │ │ │ │ │ ├── interface.go │ │ │ │ │ │ ├── metrics.go │ │ │ │ │ │ ├── timing_ratio_histogram.go │ │ │ │ │ │ ├── union_gauge.go │ │ │ │ │ │ └── vec_element_pair.go │ │ │ │ │ ├── request/ │ │ │ │ │ │ ├── config.go │ │ │ │ │ │ ├── list_work_estimator.go │ │ │ │ │ │ ├── mutating_work_estimator.go │ │ │ │ │ │ ├── object_count_tracker.go │ │ │ │ │ │ ├── seat_seconds.go │ │ │ │ │ │ └── width.go │ │ │ │ │ ├── rule.go │ │ │ │ │ └── watch_tracker.go │ │ │ │ ├── flushwriter/ │ │ │ │ │ ├── doc.go │ │ │ │ │ └── writer.go │ │ │ │ ├── peerproxy/ │ │ │ │ │ └── metrics/ │ │ │ │ │ └── metrics.go │ │ │ │ ├── shufflesharding/ │ │ │ │ │ └── shufflesharding.go │ │ │ │ ├── webhook/ │ │ │ │ │ ├── authentication.go │ │ │ │ │ ├── client.go │ │ │ │ │ ├── error.go │ │ │ │ │ ├── gencerts.sh │ │ │ │ │ ├── metrics.go │ │ │ │ │ ├── serviceresolver.go │ │ │ │ │ ├── validation.go │ │ │ │ │ └── webhook.go │ │ │ │ └── x509metrics/ │ │ │ │ └── server_cert_deprecations.go │ │ │ └── warning/ │ │ │ └── context.go │ │ └── plugin/ │ │ └── pkg/ │ │ └── authenticator/ │ │ └── token/ │ │ └── webhook/ │ │ ├── metrics.go │ │ └── webhook.go │ ├── cli-runtime/ │ │ ├── LICENSE │ │ └── pkg/ │ │ └── printers/ │ │ ├── discard.go │ │ ├── doc.go │ │ ├── interface.go │ │ ├── json.go │ │ ├── jsonpath.go │ │ ├── managedfields.go │ │ ├── name.go │ │ ├── sourcechecker.go │ │ ├── tableprinter.go │ │ ├── tabwriter.go │ │ ├── template.go │ │ ├── terminal.go │ │ ├── typesetter.go │ │ ├── warningprinter.go │ │ └── yaml.go │ ├── client-go/ │ │ ├── LICENSE │ │ ├── applyconfigurations/ │ │ │ ├── OWNERS │ │ │ ├── admissionregistration/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── auditannotation.go │ │ │ │ │ ├── expressionwarning.go │ │ │ │ │ ├── matchcondition.go │ │ │ │ │ ├── matchresources.go │ │ │ │ │ ├── mutatingwebhook.go │ │ │ │ │ ├── mutatingwebhookconfiguration.go │ │ │ │ │ ├── namedrulewithoperations.go │ │ │ │ │ ├── paramkind.go │ │ │ │ │ ├── paramref.go │ │ │ │ │ ├── rule.go │ │ │ │ │ ├── rulewithoperations.go │ │ │ │ │ ├── servicereference.go │ │ │ │ │ ├── typechecking.go │ │ │ │ │ ├── validatingadmissionpolicy.go │ │ │ │ │ ├── validatingadmissionpolicybinding.go │ │ │ │ │ ├── validatingadmissionpolicybindingspec.go │ │ │ │ │ ├── validatingadmissionpolicyspec.go │ │ │ │ │ ├── validatingadmissionpolicystatus.go │ │ │ │ │ ├── validatingwebhook.go │ │ │ │ │ ├── validatingwebhookconfiguration.go │ │ │ │ │ ├── validation.go │ │ │ │ │ ├── variable.go │ │ │ │ │ └── webhookclientconfig.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── applyconfiguration.go │ │ │ │ │ ├── auditannotation.go │ │ │ │ │ ├── expressionwarning.go │ │ │ │ │ ├── jsonpatch.go │ │ │ │ │ ├── matchcondition.go │ │ │ │ │ ├── matchresources.go │ │ │ │ │ ├── mutatingadmissionpolicy.go │ │ │ │ │ ├── mutatingadmissionpolicybinding.go │ │ │ │ │ ├── mutatingadmissionpolicybindingspec.go │ │ │ │ │ ├── mutatingadmissionpolicyspec.go │ │ │ │ │ ├── mutation.go │ │ │ │ │ ├── namedrulewithoperations.go │ │ │ │ │ ├── paramkind.go │ │ │ │ │ ├── paramref.go │ │ │ │ │ ├── typechecking.go │ │ │ │ │ ├── validatingadmissionpolicy.go │ │ │ │ │ ├── validatingadmissionpolicybinding.go │ │ │ │ │ ├── validatingadmissionpolicybindingspec.go │ │ │ │ │ ├── validatingadmissionpolicyspec.go │ │ │ │ │ ├── validatingadmissionpolicystatus.go │ │ │ │ │ ├── validation.go │ │ │ │ │ └── variable.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── auditannotation.go │ │ │ │ ├── expressionwarning.go │ │ │ │ ├── matchcondition.go │ │ │ │ ├── matchresources.go │ │ │ │ ├── mutatingwebhook.go │ │ │ │ ├── mutatingwebhookconfiguration.go │ │ │ │ ├── namedrulewithoperations.go │ │ │ │ ├── paramkind.go │ │ │ │ ├── paramref.go │ │ │ │ ├── servicereference.go │ │ │ │ ├── typechecking.go │ │ │ │ ├── validatingadmissionpolicy.go │ │ │ │ ├── validatingadmissionpolicybinding.go │ │ │ │ ├── validatingadmissionpolicybindingspec.go │ │ │ │ ├── validatingadmissionpolicyspec.go │ │ │ │ ├── validatingadmissionpolicystatus.go │ │ │ │ ├── validatingwebhook.go │ │ │ │ ├── validatingwebhookconfiguration.go │ │ │ │ ├── validation.go │ │ │ │ ├── variable.go │ │ │ │ └── webhookclientconfig.go │ │ │ ├── apiserverinternal/ │ │ │ │ └── v1alpha1/ │ │ │ │ ├── serverstorageversion.go │ │ │ │ ├── storageversion.go │ │ │ │ ├── storageversioncondition.go │ │ │ │ └── storageversionstatus.go │ │ │ ├── apps/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── controllerrevision.go │ │ │ │ │ ├── daemonset.go │ │ │ │ │ ├── daemonsetcondition.go │ │ │ │ │ ├── daemonsetspec.go │ │ │ │ │ ├── daemonsetstatus.go │ │ │ │ │ ├── daemonsetupdatestrategy.go │ │ │ │ │ ├── deployment.go │ │ │ │ │ ├── deploymentcondition.go │ │ │ │ │ ├── deploymentspec.go │ │ │ │ │ ├── deploymentstatus.go │ │ │ │ │ ├── deploymentstrategy.go │ │ │ │ │ ├── replicaset.go │ │ │ │ │ ├── replicasetcondition.go │ │ │ │ │ ├── replicasetspec.go │ │ │ │ │ ├── replicasetstatus.go │ │ │ │ │ ├── rollingupdatedaemonset.go │ │ │ │ │ ├── rollingupdatedeployment.go │ │ │ │ │ ├── rollingupdatestatefulsetstrategy.go │ │ │ │ │ ├── statefulset.go │ │ │ │ │ ├── statefulsetcondition.go │ │ │ │ │ ├── statefulsetordinals.go │ │ │ │ │ ├── statefulsetpersistentvolumeclaimretentionpolicy.go │ │ │ │ │ ├── statefulsetspec.go │ │ │ │ │ ├── statefulsetstatus.go │ │ │ │ │ └── statefulsetupdatestrategy.go │ │ │ │ ├── v1beta1/ │ │ │ │ │ ├── controllerrevision.go │ │ │ │ │ ├── deployment.go │ │ │ │ │ ├── deploymentcondition.go │ │ │ │ │ ├── deploymentspec.go │ │ │ │ │ ├── deploymentstatus.go │ │ │ │ │ ├── deploymentstrategy.go │ │ │ │ │ ├── rollbackconfig.go │ │ │ │ │ ├── rollingupdatedeployment.go │ │ │ │ │ ├── rollingupdatestatefulsetstrategy.go │ │ │ │ │ ├── statefulset.go │ │ │ │ │ ├── statefulsetcondition.go │ │ │ │ │ ├── statefulsetordinals.go │ │ │ │ │ ├── statefulsetpersistentvolumeclaimretentionpolicy.go │ │ │ │ │ ├── statefulsetspec.go │ │ │ │ │ ├── statefulsetstatus.go │ │ │ │ │ └── statefulsetupdatestrategy.go │ │ │ │ └── v1beta2/ │ │ │ │ ├── controllerrevision.go │ │ │ │ ├── daemonset.go │ │ │ │ ├── daemonsetcondition.go │ │ │ │ ├── daemonsetspec.go │ │ │ │ ├── daemonsetstatus.go │ │ │ │ ├── daemonsetupdatestrategy.go │ │ │ │ ├── deployment.go │ │ │ │ ├── deploymentcondition.go │ │ │ │ ├── deploymentspec.go │ │ │ │ ├── deploymentstatus.go │ │ │ │ ├── deploymentstrategy.go │ │ │ │ ├── replicaset.go │ │ │ │ ├── replicasetcondition.go │ │ │ │ ├── replicasetspec.go │ │ │ │ ├── replicasetstatus.go │ │ │ │ ├── rollingupdatedaemonset.go │ │ │ │ ├── rollingupdatedeployment.go │ │ │ │ ├── rollingupdatestatefulsetstrategy.go │ │ │ │ ├── scale.go │ │ │ │ ├── statefulset.go │ │ │ │ ├── statefulsetcondition.go │ │ │ │ ├── statefulsetordinals.go │ │ │ │ ├── statefulsetpersistentvolumeclaimretentionpolicy.go │ │ │ │ ├── statefulsetspec.go │ │ │ │ ├── statefulsetstatus.go │ │ │ │ └── statefulsetupdatestrategy.go │ │ │ ├── autoscaling/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── crossversionobjectreference.go │ │ │ │ │ ├── horizontalpodautoscaler.go │ │ │ │ │ ├── horizontalpodautoscalerspec.go │ │ │ │ │ ├── horizontalpodautoscalerstatus.go │ │ │ │ │ ├── scale.go │ │ │ │ │ ├── scalespec.go │ │ │ │ │ └── scalestatus.go │ │ │ │ ├── v2/ │ │ │ │ │ ├── containerresourcemetricsource.go │ │ │ │ │ ├── containerresourcemetricstatus.go │ │ │ │ │ ├── crossversionobjectreference.go │ │ │ │ │ ├── externalmetricsource.go │ │ │ │ │ ├── externalmetricstatus.go │ │ │ │ │ ├── horizontalpodautoscaler.go │ │ │ │ │ ├── horizontalpodautoscalerbehavior.go │ │ │ │ │ ├── horizontalpodautoscalercondition.go │ │ │ │ │ ├── horizontalpodautoscalerspec.go │ │ │ │ │ ├── horizontalpodautoscalerstatus.go │ │ │ │ │ ├── hpascalingpolicy.go │ │ │ │ │ ├── hpascalingrules.go │ │ │ │ │ ├── metricidentifier.go │ │ │ │ │ ├── metricspec.go │ │ │ │ │ ├── metricstatus.go │ │ │ │ │ ├── metrictarget.go │ │ │ │ │ ├── metricvaluestatus.go │ │ │ │ │ ├── objectmetricsource.go │ │ │ │ │ ├── objectmetricstatus.go │ │ │ │ │ ├── podsmetricsource.go │ │ │ │ │ ├── podsmetricstatus.go │ │ │ │ │ ├── resourcemetricsource.go │ │ │ │ │ └── resourcemetricstatus.go │ │ │ │ ├── v2beta1/ │ │ │ │ │ ├── containerresourcemetricsource.go │ │ │ │ │ ├── containerresourcemetricstatus.go │ │ │ │ │ ├── crossversionobjectreference.go │ │ │ │ │ ├── externalmetricsource.go │ │ │ │ │ ├── externalmetricstatus.go │ │ │ │ │ ├── horizontalpodautoscaler.go │ │ │ │ │ ├── horizontalpodautoscalercondition.go │ │ │ │ │ ├── horizontalpodautoscalerspec.go │ │ │ │ │ ├── horizontalpodautoscalerstatus.go │ │ │ │ │ ├── metricspec.go │ │ │ │ │ ├── metricstatus.go │ │ │ │ │ ├── objectmetricsource.go │ │ │ │ │ ├── objectmetricstatus.go │ │ │ │ │ ├── podsmetricsource.go │ │ │ │ │ ├── podsmetricstatus.go │ │ │ │ │ ├── resourcemetricsource.go │ │ │ │ │ └── resourcemetricstatus.go │ │ │ │ └── v2beta2/ │ │ │ │ ├── containerresourcemetricsource.go │ │ │ │ ├── containerresourcemetricstatus.go │ │ │ │ ├── crossversionobjectreference.go │ │ │ │ ├── externalmetricsource.go │ │ │ │ ├── externalmetricstatus.go │ │ │ │ ├── horizontalpodautoscaler.go │ │ │ │ ├── horizontalpodautoscalerbehavior.go │ │ │ │ ├── horizontalpodautoscalercondition.go │ │ │ │ ├── horizontalpodautoscalerspec.go │ │ │ │ ├── horizontalpodautoscalerstatus.go │ │ │ │ ├── hpascalingpolicy.go │ │ │ │ ├── hpascalingrules.go │ │ │ │ ├── metricidentifier.go │ │ │ │ ├── metricspec.go │ │ │ │ ├── metricstatus.go │ │ │ │ ├── metrictarget.go │ │ │ │ ├── metricvaluestatus.go │ │ │ │ ├── objectmetricsource.go │ │ │ │ ├── objectmetricstatus.go │ │ │ │ ├── podsmetricsource.go │ │ │ │ ├── podsmetricstatus.go │ │ │ │ ├── resourcemetricsource.go │ │ │ │ └── resourcemetricstatus.go │ │ │ ├── batch/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── cronjob.go │ │ │ │ │ ├── cronjobspec.go │ │ │ │ │ ├── cronjobstatus.go │ │ │ │ │ ├── job.go │ │ │ │ │ ├── jobcondition.go │ │ │ │ │ ├── jobspec.go │ │ │ │ │ ├── jobstatus.go │ │ │ │ │ ├── jobtemplatespec.go │ │ │ │ │ ├── podfailurepolicy.go │ │ │ │ │ ├── podfailurepolicyonexitcodesrequirement.go │ │ │ │ │ ├── podfailurepolicyonpodconditionspattern.go │ │ │ │ │ ├── podfailurepolicyrule.go │ │ │ │ │ ├── successpolicy.go │ │ │ │ │ ├── successpolicyrule.go │ │ │ │ │ └── uncountedterminatedpods.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── cronjob.go │ │ │ │ ├── cronjobspec.go │ │ │ │ ├── cronjobstatus.go │ │ │ │ └── jobtemplatespec.go │ │ │ ├── certificates/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── certificatesigningrequest.go │ │ │ │ │ ├── certificatesigningrequestcondition.go │ │ │ │ │ ├── certificatesigningrequestspec.go │ │ │ │ │ └── certificatesigningrequeststatus.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── clustertrustbundle.go │ │ │ │ │ └── clustertrustbundlespec.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── certificatesigningrequest.go │ │ │ │ ├── certificatesigningrequestcondition.go │ │ │ │ ├── certificatesigningrequestspec.go │ │ │ │ └── certificatesigningrequeststatus.go │ │ │ ├── coordination/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── lease.go │ │ │ │ │ └── leasespec.go │ │ │ │ ├── v1alpha2/ │ │ │ │ │ ├── leasecandidate.go │ │ │ │ │ └── leasecandidatespec.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── lease.go │ │ │ │ └── leasespec.go │ │ │ ├── core/ │ │ │ │ └── v1/ │ │ │ │ ├── affinity.go │ │ │ │ ├── apparmorprofile.go │ │ │ │ ├── attachedvolume.go │ │ │ │ ├── awselasticblockstorevolumesource.go │ │ │ │ ├── azurediskvolumesource.go │ │ │ │ ├── azurefilepersistentvolumesource.go │ │ │ │ ├── azurefilevolumesource.go │ │ │ │ ├── capabilities.go │ │ │ │ ├── cephfspersistentvolumesource.go │ │ │ │ ├── cephfsvolumesource.go │ │ │ │ ├── cinderpersistentvolumesource.go │ │ │ │ ├── cindervolumesource.go │ │ │ │ ├── clientipconfig.go │ │ │ │ ├── clustertrustbundleprojection.go │ │ │ │ ├── componentcondition.go │ │ │ │ ├── componentstatus.go │ │ │ │ ├── configmap.go │ │ │ │ ├── configmapenvsource.go │ │ │ │ ├── configmapkeyselector.go │ │ │ │ ├── configmapnodeconfigsource.go │ │ │ │ ├── configmapprojection.go │ │ │ │ ├── configmapvolumesource.go │ │ │ │ ├── container.go │ │ │ │ ├── containerimage.go │ │ │ │ ├── containerport.go │ │ │ │ ├── containerresizepolicy.go │ │ │ │ ├── containerstate.go │ │ │ │ ├── containerstaterunning.go │ │ │ │ ├── containerstateterminated.go │ │ │ │ ├── containerstatewaiting.go │ │ │ │ ├── containerstatus.go │ │ │ │ ├── containeruser.go │ │ │ │ ├── csipersistentvolumesource.go │ │ │ │ ├── csivolumesource.go │ │ │ │ ├── daemonendpoint.go │ │ │ │ ├── downwardapiprojection.go │ │ │ │ ├── downwardapivolumefile.go │ │ │ │ ├── downwardapivolumesource.go │ │ │ │ ├── emptydirvolumesource.go │ │ │ │ ├── endpointaddress.go │ │ │ │ ├── endpointport.go │ │ │ │ ├── endpoints.go │ │ │ │ ├── endpointsubset.go │ │ │ │ ├── envfromsource.go │ │ │ │ ├── envvar.go │ │ │ │ ├── envvarsource.go │ │ │ │ ├── ephemeralcontainer.go │ │ │ │ ├── ephemeralcontainercommon.go │ │ │ │ ├── ephemeralvolumesource.go │ │ │ │ ├── event.go │ │ │ │ ├── eventseries.go │ │ │ │ ├── eventsource.go │ │ │ │ ├── execaction.go │ │ │ │ ├── fcvolumesource.go │ │ │ │ ├── flexpersistentvolumesource.go │ │ │ │ ├── flexvolumesource.go │ │ │ │ ├── flockervolumesource.go │ │ │ │ ├── gcepersistentdiskvolumesource.go │ │ │ │ ├── gitrepovolumesource.go │ │ │ │ ├── glusterfspersistentvolumesource.go │ │ │ │ ├── glusterfsvolumesource.go │ │ │ │ ├── grpcaction.go │ │ │ │ ├── hostalias.go │ │ │ │ ├── hostip.go │ │ │ │ ├── hostpathvolumesource.go │ │ │ │ ├── httpgetaction.go │ │ │ │ ├── httpheader.go │ │ │ │ ├── imagevolumesource.go │ │ │ │ ├── iscsipersistentvolumesource.go │ │ │ │ ├── iscsivolumesource.go │ │ │ │ ├── keytopath.go │ │ │ │ ├── lifecycle.go │ │ │ │ ├── lifecyclehandler.go │ │ │ │ ├── limitrange.go │ │ │ │ ├── limitrangeitem.go │ │ │ │ ├── limitrangespec.go │ │ │ │ ├── linuxcontaineruser.go │ │ │ │ ├── loadbalanceringress.go │ │ │ │ ├── loadbalancerstatus.go │ │ │ │ ├── localobjectreference.go │ │ │ │ ├── localvolumesource.go │ │ │ │ ├── modifyvolumestatus.go │ │ │ │ ├── namespace.go │ │ │ │ ├── namespacecondition.go │ │ │ │ ├── namespacespec.go │ │ │ │ ├── namespacestatus.go │ │ │ │ ├── nfsvolumesource.go │ │ │ │ ├── node.go │ │ │ │ ├── nodeaddress.go │ │ │ │ ├── nodeaffinity.go │ │ │ │ ├── nodecondition.go │ │ │ │ ├── nodeconfigsource.go │ │ │ │ ├── nodeconfigstatus.go │ │ │ │ ├── nodedaemonendpoints.go │ │ │ │ ├── nodefeatures.go │ │ │ │ ├── noderuntimehandler.go │ │ │ │ ├── noderuntimehandlerfeatures.go │ │ │ │ ├── nodeselector.go │ │ │ │ ├── nodeselectorrequirement.go │ │ │ │ ├── nodeselectorterm.go │ │ │ │ ├── nodespec.go │ │ │ │ ├── nodestatus.go │ │ │ │ ├── nodesysteminfo.go │ │ │ │ ├── objectfieldselector.go │ │ │ │ ├── objectreference.go │ │ │ │ ├── persistentvolume.go │ │ │ │ ├── persistentvolumeclaim.go │ │ │ │ ├── persistentvolumeclaimcondition.go │ │ │ │ ├── persistentvolumeclaimspec.go │ │ │ │ ├── persistentvolumeclaimstatus.go │ │ │ │ ├── persistentvolumeclaimtemplate.go │ │ │ │ ├── persistentvolumeclaimvolumesource.go │ │ │ │ ├── persistentvolumesource.go │ │ │ │ ├── persistentvolumespec.go │ │ │ │ ├── persistentvolumestatus.go │ │ │ │ ├── photonpersistentdiskvolumesource.go │ │ │ │ ├── pod.go │ │ │ │ ├── podaffinity.go │ │ │ │ ├── podaffinityterm.go │ │ │ │ ├── podantiaffinity.go │ │ │ │ ├── podcondition.go │ │ │ │ ├── poddnsconfig.go │ │ │ │ ├── poddnsconfigoption.go │ │ │ │ ├── podip.go │ │ │ │ ├── podos.go │ │ │ │ ├── podreadinessgate.go │ │ │ │ ├── podresourceclaim.go │ │ │ │ ├── podresourceclaimstatus.go │ │ │ │ ├── podschedulinggate.go │ │ │ │ ├── podsecuritycontext.go │ │ │ │ ├── podspec.go │ │ │ │ ├── podstatus.go │ │ │ │ ├── podtemplate.go │ │ │ │ ├── podtemplatespec.go │ │ │ │ ├── portstatus.go │ │ │ │ ├── portworxvolumesource.go │ │ │ │ ├── preferredschedulingterm.go │ │ │ │ ├── probe.go │ │ │ │ ├── probehandler.go │ │ │ │ ├── projectedvolumesource.go │ │ │ │ ├── quobytevolumesource.go │ │ │ │ ├── rbdpersistentvolumesource.go │ │ │ │ ├── rbdvolumesource.go │ │ │ │ ├── replicationcontroller.go │ │ │ │ ├── replicationcontrollercondition.go │ │ │ │ ├── replicationcontrollerspec.go │ │ │ │ ├── replicationcontrollerstatus.go │ │ │ │ ├── resourceclaim.go │ │ │ │ ├── resourcefieldselector.go │ │ │ │ ├── resourcehealth.go │ │ │ │ ├── resourcequota.go │ │ │ │ ├── resourcequotaspec.go │ │ │ │ ├── resourcequotastatus.go │ │ │ │ ├── resourcerequirements.go │ │ │ │ ├── resourcestatus.go │ │ │ │ ├── scaleiopersistentvolumesource.go │ │ │ │ ├── scaleiovolumesource.go │ │ │ │ ├── scopedresourceselectorrequirement.go │ │ │ │ ├── scopeselector.go │ │ │ │ ├── seccompprofile.go │ │ │ │ ├── secret.go │ │ │ │ ├── secretenvsource.go │ │ │ │ ├── secretkeyselector.go │ │ │ │ ├── secretprojection.go │ │ │ │ ├── secretreference.go │ │ │ │ ├── secretvolumesource.go │ │ │ │ ├── securitycontext.go │ │ │ │ ├── selinuxoptions.go │ │ │ │ ├── service.go │ │ │ │ ├── serviceaccount.go │ │ │ │ ├── serviceaccounttokenprojection.go │ │ │ │ ├── serviceport.go │ │ │ │ ├── servicespec.go │ │ │ │ ├── servicestatus.go │ │ │ │ ├── sessionaffinityconfig.go │ │ │ │ ├── sleepaction.go │ │ │ │ ├── storageospersistentvolumesource.go │ │ │ │ ├── storageosvolumesource.go │ │ │ │ ├── sysctl.go │ │ │ │ ├── taint.go │ │ │ │ ├── tcpsocketaction.go │ │ │ │ ├── toleration.go │ │ │ │ ├── topologyselectorlabelrequirement.go │ │ │ │ ├── topologyselectorterm.go │ │ │ │ ├── topologyspreadconstraint.go │ │ │ │ ├── typedlocalobjectreference.go │ │ │ │ ├── typedobjectreference.go │ │ │ │ ├── volume.go │ │ │ │ ├── volumedevice.go │ │ │ │ ├── volumemount.go │ │ │ │ ├── volumemountstatus.go │ │ │ │ ├── volumenodeaffinity.go │ │ │ │ ├── volumeprojection.go │ │ │ │ ├── volumeresourcerequirements.go │ │ │ │ ├── volumesource.go │ │ │ │ ├── vspherevirtualdiskvolumesource.go │ │ │ │ ├── weightedpodaffinityterm.go │ │ │ │ └── windowssecuritycontextoptions.go │ │ │ ├── discovery/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── endpoint.go │ │ │ │ │ ├── endpointconditions.go │ │ │ │ │ ├── endpointhints.go │ │ │ │ │ ├── endpointport.go │ │ │ │ │ ├── endpointslice.go │ │ │ │ │ └── forzone.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── endpoint.go │ │ │ │ ├── endpointconditions.go │ │ │ │ ├── endpointhints.go │ │ │ │ ├── endpointport.go │ │ │ │ ├── endpointslice.go │ │ │ │ └── forzone.go │ │ │ ├── doc.go │ │ │ ├── events/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── event.go │ │ │ │ │ └── eventseries.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── event.go │ │ │ │ └── eventseries.go │ │ │ ├── extensions/ │ │ │ │ └── v1beta1/ │ │ │ │ ├── daemonset.go │ │ │ │ ├── daemonsetcondition.go │ │ │ │ ├── daemonsetspec.go │ │ │ │ ├── daemonsetstatus.go │ │ │ │ ├── daemonsetupdatestrategy.go │ │ │ │ ├── deployment.go │ │ │ │ ├── deploymentcondition.go │ │ │ │ ├── deploymentspec.go │ │ │ │ ├── deploymentstatus.go │ │ │ │ ├── deploymentstrategy.go │ │ │ │ ├── httpingresspath.go │ │ │ │ ├── httpingressrulevalue.go │ │ │ │ ├── ingress.go │ │ │ │ ├── ingressbackend.go │ │ │ │ ├── ingressloadbalanceringress.go │ │ │ │ ├── ingressloadbalancerstatus.go │ │ │ │ ├── ingressportstatus.go │ │ │ │ ├── ingressrule.go │ │ │ │ ├── ingressrulevalue.go │ │ │ │ ├── ingressspec.go │ │ │ │ ├── ingressstatus.go │ │ │ │ ├── ingresstls.go │ │ │ │ ├── ipblock.go │ │ │ │ ├── networkpolicy.go │ │ │ │ ├── networkpolicyegressrule.go │ │ │ │ ├── networkpolicyingressrule.go │ │ │ │ ├── networkpolicypeer.go │ │ │ │ ├── networkpolicyport.go │ │ │ │ ├── networkpolicyspec.go │ │ │ │ ├── replicaset.go │ │ │ │ ├── replicasetcondition.go │ │ │ │ ├── replicasetspec.go │ │ │ │ ├── replicasetstatus.go │ │ │ │ ├── rollbackconfig.go │ │ │ │ ├── rollingupdatedaemonset.go │ │ │ │ ├── rollingupdatedeployment.go │ │ │ │ └── scale.go │ │ │ ├── flowcontrol/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── exemptprioritylevelconfiguration.go │ │ │ │ │ ├── flowdistinguishermethod.go │ │ │ │ │ ├── flowschema.go │ │ │ │ │ ├── flowschemacondition.go │ │ │ │ │ ├── flowschemaspec.go │ │ │ │ │ ├── flowschemastatus.go │ │ │ │ │ ├── groupsubject.go │ │ │ │ │ ├── limitedprioritylevelconfiguration.go │ │ │ │ │ ├── limitresponse.go │ │ │ │ │ ├── nonresourcepolicyrule.go │ │ │ │ │ ├── policyruleswithsubjects.go │ │ │ │ │ ├── prioritylevelconfiguration.go │ │ │ │ │ ├── prioritylevelconfigurationcondition.go │ │ │ │ │ ├── prioritylevelconfigurationreference.go │ │ │ │ │ ├── prioritylevelconfigurationspec.go │ │ │ │ │ ├── prioritylevelconfigurationstatus.go │ │ │ │ │ ├── queuingconfiguration.go │ │ │ │ │ ├── resourcepolicyrule.go │ │ │ │ │ ├── serviceaccountsubject.go │ │ │ │ │ ├── subject.go │ │ │ │ │ └── usersubject.go │ │ │ │ ├── v1beta1/ │ │ │ │ │ ├── exemptprioritylevelconfiguration.go │ │ │ │ │ ├── flowdistinguishermethod.go │ │ │ │ │ ├── flowschema.go │ │ │ │ │ ├── flowschemacondition.go │ │ │ │ │ ├── flowschemaspec.go │ │ │ │ │ ├── flowschemastatus.go │ │ │ │ │ ├── groupsubject.go │ │ │ │ │ ├── limitedprioritylevelconfiguration.go │ │ │ │ │ ├── limitresponse.go │ │ │ │ │ ├── nonresourcepolicyrule.go │ │ │ │ │ ├── policyruleswithsubjects.go │ │ │ │ │ ├── prioritylevelconfiguration.go │ │ │ │ │ ├── prioritylevelconfigurationcondition.go │ │ │ │ │ ├── prioritylevelconfigurationreference.go │ │ │ │ │ ├── prioritylevelconfigurationspec.go │ │ │ │ │ ├── prioritylevelconfigurationstatus.go │ │ │ │ │ ├── queuingconfiguration.go │ │ │ │ │ ├── resourcepolicyrule.go │ │ │ │ │ ├── serviceaccountsubject.go │ │ │ │ │ ├── subject.go │ │ │ │ │ └── usersubject.go │ │ │ │ ├── v1beta2/ │ │ │ │ │ ├── exemptprioritylevelconfiguration.go │ │ │ │ │ ├── flowdistinguishermethod.go │ │ │ │ │ ├── flowschema.go │ │ │ │ │ ├── flowschemacondition.go │ │ │ │ │ ├── flowschemaspec.go │ │ │ │ │ ├── flowschemastatus.go │ │ │ │ │ ├── groupsubject.go │ │ │ │ │ ├── limitedprioritylevelconfiguration.go │ │ │ │ │ ├── limitresponse.go │ │ │ │ │ ├── nonresourcepolicyrule.go │ │ │ │ │ ├── policyruleswithsubjects.go │ │ │ │ │ ├── prioritylevelconfiguration.go │ │ │ │ │ ├── prioritylevelconfigurationcondition.go │ │ │ │ │ ├── prioritylevelconfigurationreference.go │ │ │ │ │ ├── prioritylevelconfigurationspec.go │ │ │ │ │ ├── prioritylevelconfigurationstatus.go │ │ │ │ │ ├── queuingconfiguration.go │ │ │ │ │ ├── resourcepolicyrule.go │ │ │ │ │ ├── serviceaccountsubject.go │ │ │ │ │ ├── subject.go │ │ │ │ │ └── usersubject.go │ │ │ │ └── v1beta3/ │ │ │ │ ├── exemptprioritylevelconfiguration.go │ │ │ │ ├── flowdistinguishermethod.go │ │ │ │ ├── flowschema.go │ │ │ │ ├── flowschemacondition.go │ │ │ │ ├── flowschemaspec.go │ │ │ │ ├── flowschemastatus.go │ │ │ │ ├── groupsubject.go │ │ │ │ ├── limitedprioritylevelconfiguration.go │ │ │ │ ├── limitresponse.go │ │ │ │ ├── nonresourcepolicyrule.go │ │ │ │ ├── policyruleswithsubjects.go │ │ │ │ ├── prioritylevelconfiguration.go │ │ │ │ ├── prioritylevelconfigurationcondition.go │ │ │ │ ├── prioritylevelconfigurationreference.go │ │ │ │ ├── prioritylevelconfigurationspec.go │ │ │ │ ├── prioritylevelconfigurationstatus.go │ │ │ │ ├── queuingconfiguration.go │ │ │ │ ├── resourcepolicyrule.go │ │ │ │ ├── serviceaccountsubject.go │ │ │ │ ├── subject.go │ │ │ │ └── usersubject.go │ │ │ ├── imagepolicy/ │ │ │ │ └── v1alpha1/ │ │ │ │ ├── imagereview.go │ │ │ │ ├── imagereviewcontainerspec.go │ │ │ │ ├── imagereviewspec.go │ │ │ │ └── imagereviewstatus.go │ │ │ ├── internal/ │ │ │ │ └── internal.go │ │ │ ├── meta/ │ │ │ │ └── v1/ │ │ │ │ ├── condition.go │ │ │ │ ├── deleteoptions.go │ │ │ │ ├── labelselector.go │ │ │ │ ├── labelselectorrequirement.go │ │ │ │ ├── managedfieldsentry.go │ │ │ │ ├── objectmeta.go │ │ │ │ ├── ownerreference.go │ │ │ │ ├── preconditions.go │ │ │ │ ├── typemeta.go │ │ │ │ └── unstructured.go │ │ │ ├── networking/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── httpingresspath.go │ │ │ │ │ ├── httpingressrulevalue.go │ │ │ │ │ ├── ingress.go │ │ │ │ │ ├── ingressbackend.go │ │ │ │ │ ├── ingressclass.go │ │ │ │ │ ├── ingressclassparametersreference.go │ │ │ │ │ ├── ingressclassspec.go │ │ │ │ │ ├── ingressloadbalanceringress.go │ │ │ │ │ ├── ingressloadbalancerstatus.go │ │ │ │ │ ├── ingressportstatus.go │ │ │ │ │ ├── ingressrule.go │ │ │ │ │ ├── ingressrulevalue.go │ │ │ │ │ ├── ingressservicebackend.go │ │ │ │ │ ├── ingressspec.go │ │ │ │ │ ├── ingressstatus.go │ │ │ │ │ ├── ingresstls.go │ │ │ │ │ ├── ipblock.go │ │ │ │ │ ├── networkpolicy.go │ │ │ │ │ ├── networkpolicyegressrule.go │ │ │ │ │ ├── networkpolicyingressrule.go │ │ │ │ │ ├── networkpolicypeer.go │ │ │ │ │ ├── networkpolicyport.go │ │ │ │ │ ├── networkpolicyspec.go │ │ │ │ │ └── servicebackendport.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── ipaddress.go │ │ │ │ │ ├── ipaddressspec.go │ │ │ │ │ ├── parentreference.go │ │ │ │ │ ├── servicecidr.go │ │ │ │ │ ├── servicecidrspec.go │ │ │ │ │ └── servicecidrstatus.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── httpingresspath.go │ │ │ │ ├── httpingressrulevalue.go │ │ │ │ ├── ingress.go │ │ │ │ ├── ingressbackend.go │ │ │ │ ├── ingressclass.go │ │ │ │ ├── ingressclassparametersreference.go │ │ │ │ ├── ingressclassspec.go │ │ │ │ ├── ingressloadbalanceringress.go │ │ │ │ ├── ingressloadbalancerstatus.go │ │ │ │ ├── ingressportstatus.go │ │ │ │ ├── ingressrule.go │ │ │ │ ├── ingressrulevalue.go │ │ │ │ ├── ingressspec.go │ │ │ │ ├── ingressstatus.go │ │ │ │ ├── ingresstls.go │ │ │ │ ├── ipaddress.go │ │ │ │ ├── ipaddressspec.go │ │ │ │ ├── parentreference.go │ │ │ │ ├── servicecidr.go │ │ │ │ ├── servicecidrspec.go │ │ │ │ └── servicecidrstatus.go │ │ │ ├── node/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── overhead.go │ │ │ │ │ ├── runtimeclass.go │ │ │ │ │ └── scheduling.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── overhead.go │ │ │ │ │ ├── runtimeclass.go │ │ │ │ │ ├── runtimeclassspec.go │ │ │ │ │ └── scheduling.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── overhead.go │ │ │ │ ├── runtimeclass.go │ │ │ │ └── scheduling.go │ │ │ ├── policy/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── eviction.go │ │ │ │ │ ├── poddisruptionbudget.go │ │ │ │ │ ├── poddisruptionbudgetspec.go │ │ │ │ │ └── poddisruptionbudgetstatus.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── eviction.go │ │ │ │ ├── poddisruptionbudget.go │ │ │ │ ├── poddisruptionbudgetspec.go │ │ │ │ └── poddisruptionbudgetstatus.go │ │ │ ├── rbac/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── aggregationrule.go │ │ │ │ │ ├── clusterrole.go │ │ │ │ │ ├── clusterrolebinding.go │ │ │ │ │ ├── policyrule.go │ │ │ │ │ ├── role.go │ │ │ │ │ ├── rolebinding.go │ │ │ │ │ ├── roleref.go │ │ │ │ │ └── subject.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── aggregationrule.go │ │ │ │ │ ├── clusterrole.go │ │ │ │ │ ├── clusterrolebinding.go │ │ │ │ │ ├── policyrule.go │ │ │ │ │ ├── role.go │ │ │ │ │ ├── rolebinding.go │ │ │ │ │ ├── roleref.go │ │ │ │ │ └── subject.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── aggregationrule.go │ │ │ │ ├── clusterrole.go │ │ │ │ ├── clusterrolebinding.go │ │ │ │ ├── policyrule.go │ │ │ │ ├── role.go │ │ │ │ ├── rolebinding.go │ │ │ │ ├── roleref.go │ │ │ │ └── subject.go │ │ │ ├── resource/ │ │ │ │ ├── v1alpha3/ │ │ │ │ │ ├── allocateddevicestatus.go │ │ │ │ │ ├── allocationresult.go │ │ │ │ │ ├── basicdevice.go │ │ │ │ │ ├── celdeviceselector.go │ │ │ │ │ ├── device.go │ │ │ │ │ ├── deviceallocationconfiguration.go │ │ │ │ │ ├── deviceallocationresult.go │ │ │ │ │ ├── deviceattribute.go │ │ │ │ │ ├── deviceclaim.go │ │ │ │ │ ├── deviceclaimconfiguration.go │ │ │ │ │ ├── deviceclass.go │ │ │ │ │ ├── deviceclassconfiguration.go │ │ │ │ │ ├── deviceclassspec.go │ │ │ │ │ ├── deviceconfiguration.go │ │ │ │ │ ├── deviceconstraint.go │ │ │ │ │ ├── devicerequest.go │ │ │ │ │ ├── devicerequestallocationresult.go │ │ │ │ │ ├── deviceselector.go │ │ │ │ │ ├── networkdevicedata.go │ │ │ │ │ ├── opaquedeviceconfiguration.go │ │ │ │ │ ├── resourceclaim.go │ │ │ │ │ ├── resourceclaimconsumerreference.go │ │ │ │ │ ├── resourceclaimspec.go │ │ │ │ │ ├── resourceclaimstatus.go │ │ │ │ │ ├── resourceclaimtemplate.go │ │ │ │ │ ├── resourceclaimtemplatespec.go │ │ │ │ │ ├── resourcepool.go │ │ │ │ │ ├── resourceslice.go │ │ │ │ │ └── resourceslicespec.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── allocateddevicestatus.go │ │ │ │ ├── allocationresult.go │ │ │ │ ├── basicdevice.go │ │ │ │ ├── celdeviceselector.go │ │ │ │ ├── device.go │ │ │ │ ├── deviceallocationconfiguration.go │ │ │ │ ├── deviceallocationresult.go │ │ │ │ ├── deviceattribute.go │ │ │ │ ├── devicecapacity.go │ │ │ │ ├── deviceclaim.go │ │ │ │ ├── deviceclaimconfiguration.go │ │ │ │ ├── deviceclass.go │ │ │ │ ├── deviceclassconfiguration.go │ │ │ │ ├── deviceclassspec.go │ │ │ │ ├── deviceconfiguration.go │ │ │ │ ├── deviceconstraint.go │ │ │ │ ├── devicerequest.go │ │ │ │ ├── devicerequestallocationresult.go │ │ │ │ ├── deviceselector.go │ │ │ │ ├── networkdevicedata.go │ │ │ │ ├── opaquedeviceconfiguration.go │ │ │ │ ├── resourceclaim.go │ │ │ │ ├── resourceclaimconsumerreference.go │ │ │ │ ├── resourceclaimspec.go │ │ │ │ ├── resourceclaimstatus.go │ │ │ │ ├── resourceclaimtemplate.go │ │ │ │ ├── resourceclaimtemplatespec.go │ │ │ │ ├── resourcepool.go │ │ │ │ ├── resourceslice.go │ │ │ │ └── resourceslicespec.go │ │ │ ├── scheduling/ │ │ │ │ ├── v1/ │ │ │ │ │ └── priorityclass.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ └── priorityclass.go │ │ │ │ └── v1beta1/ │ │ │ │ └── priorityclass.go │ │ │ ├── storage/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── csidriver.go │ │ │ │ │ ├── csidriverspec.go │ │ │ │ │ ├── csinode.go │ │ │ │ │ ├── csinodedriver.go │ │ │ │ │ ├── csinodespec.go │ │ │ │ │ ├── csistoragecapacity.go │ │ │ │ │ ├── storageclass.go │ │ │ │ │ ├── tokenrequest.go │ │ │ │ │ ├── volumeattachment.go │ │ │ │ │ ├── volumeattachmentsource.go │ │ │ │ │ ├── volumeattachmentspec.go │ │ │ │ │ ├── volumeattachmentstatus.go │ │ │ │ │ ├── volumeerror.go │ │ │ │ │ └── volumenoderesources.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── csistoragecapacity.go │ │ │ │ │ ├── volumeattachment.go │ │ │ │ │ ├── volumeattachmentsource.go │ │ │ │ │ ├── volumeattachmentspec.go │ │ │ │ │ ├── volumeattachmentstatus.go │ │ │ │ │ ├── volumeattributesclass.go │ │ │ │ │ └── volumeerror.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── csidriver.go │ │ │ │ ├── csidriverspec.go │ │ │ │ ├── csinode.go │ │ │ │ ├── csinodedriver.go │ │ │ │ ├── csinodespec.go │ │ │ │ ├── csistoragecapacity.go │ │ │ │ ├── storageclass.go │ │ │ │ ├── tokenrequest.go │ │ │ │ ├── volumeattachment.go │ │ │ │ ├── volumeattachmentsource.go │ │ │ │ ├── volumeattachmentspec.go │ │ │ │ ├── volumeattachmentstatus.go │ │ │ │ ├── volumeattributesclass.go │ │ │ │ ├── volumeerror.go │ │ │ │ └── volumenoderesources.go │ │ │ ├── storagemigration/ │ │ │ │ └── v1alpha1/ │ │ │ │ ├── groupversionresource.go │ │ │ │ ├── migrationcondition.go │ │ │ │ ├── storageversionmigration.go │ │ │ │ ├── storageversionmigrationspec.go │ │ │ │ └── storageversionmigrationstatus.go │ │ │ └── utils.go │ │ ├── discovery/ │ │ │ ├── aggregated_discovery.go │ │ │ ├── discovery_client.go │ │ │ ├── doc.go │ │ │ ├── fake/ │ │ │ │ └── discovery.go │ │ │ └── helper.go │ │ ├── dynamic/ │ │ │ ├── dynamicinformer/ │ │ │ │ ├── informer.go │ │ │ │ └── interface.go │ │ │ ├── dynamiclister/ │ │ │ │ ├── interface.go │ │ │ │ ├── lister.go │ │ │ │ └── shim.go │ │ │ ├── fake/ │ │ │ │ └── simple.go │ │ │ ├── interface.go │ │ │ ├── scheme.go │ │ │ └── simple.go │ │ ├── features/ │ │ │ ├── envvar.go │ │ │ ├── features.go │ │ │ └── known_features.go │ │ ├── gentype/ │ │ │ ├── fake.go │ │ │ └── type.go │ │ ├── informers/ │ │ │ ├── admissionregistration/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── interface.go │ │ │ │ │ ├── mutatingwebhookconfiguration.go │ │ │ │ │ ├── validatingadmissionpolicy.go │ │ │ │ │ ├── validatingadmissionpolicybinding.go │ │ │ │ │ └── validatingwebhookconfiguration.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── interface.go │ │ │ │ │ ├── mutatingadmissionpolicy.go │ │ │ │ │ ├── mutatingadmissionpolicybinding.go │ │ │ │ │ ├── validatingadmissionpolicy.go │ │ │ │ │ └── validatingadmissionpolicybinding.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── interface.go │ │ │ │ ├── mutatingwebhookconfiguration.go │ │ │ │ ├── validatingadmissionpolicy.go │ │ │ │ ├── validatingadmissionpolicybinding.go │ │ │ │ └── validatingwebhookconfiguration.go │ │ │ ├── apiserverinternal/ │ │ │ │ ├── interface.go │ │ │ │ └── v1alpha1/ │ │ │ │ ├── interface.go │ │ │ │ └── storageversion.go │ │ │ ├── apps/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── controllerrevision.go │ │ │ │ │ ├── daemonset.go │ │ │ │ │ ├── deployment.go │ │ │ │ │ ├── interface.go │ │ │ │ │ ├── replicaset.go │ │ │ │ │ └── statefulset.go │ │ │ │ ├── v1beta1/ │ │ │ │ │ ├── controllerrevision.go │ │ │ │ │ ├── deployment.go │ │ │ │ │ ├── interface.go │ │ │ │ │ └── statefulset.go │ │ │ │ └── v1beta2/ │ │ │ │ ├── controllerrevision.go │ │ │ │ ├── daemonset.go │ │ │ │ ├── deployment.go │ │ │ │ ├── interface.go │ │ │ │ ├── replicaset.go │ │ │ │ └── statefulset.go │ │ │ ├── autoscaling/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── horizontalpodautoscaler.go │ │ │ │ │ └── interface.go │ │ │ │ ├── v2/ │ │ │ │ │ ├── horizontalpodautoscaler.go │ │ │ │ │ └── interface.go │ │ │ │ ├── v2beta1/ │ │ │ │ │ ├── horizontalpodautoscaler.go │ │ │ │ │ └── interface.go │ │ │ │ └── v2beta2/ │ │ │ │ ├── horizontalpodautoscaler.go │ │ │ │ └── interface.go │ │ │ ├── batch/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── cronjob.go │ │ │ │ │ ├── interface.go │ │ │ │ │ └── job.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── cronjob.go │ │ │ │ └── interface.go │ │ │ ├── certificates/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── certificatesigningrequest.go │ │ │ │ │ └── interface.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── clustertrustbundle.go │ │ │ │ │ └── interface.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── certificatesigningrequest.go │ │ │ │ └── interface.go │ │ │ ├── coordination/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── interface.go │ │ │ │ │ └── lease.go │ │ │ │ ├── v1alpha2/ │ │ │ │ │ ├── interface.go │ │ │ │ │ └── leasecandidate.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── interface.go │ │ │ │ └── lease.go │ │ │ ├── core/ │ │ │ │ ├── interface.go │ │ │ │ └── v1/ │ │ │ │ ├── componentstatus.go │ │ │ │ ├── configmap.go │ │ │ │ ├── endpoints.go │ │ │ │ ├── event.go │ │ │ │ ├── interface.go │ │ │ │ ├── limitrange.go │ │ │ │ ├── namespace.go │ │ │ │ ├── node.go │ │ │ │ ├── persistentvolume.go │ │ │ │ ├── persistentvolumeclaim.go │ │ │ │ ├── pod.go │ │ │ │ ├── podtemplate.go │ │ │ │ ├── replicationcontroller.go │ │ │ │ ├── resourcequota.go │ │ │ │ ├── secret.go │ │ │ │ ├── service.go │ │ │ │ └── serviceaccount.go │ │ │ ├── discovery/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── endpointslice.go │ │ │ │ │ └── interface.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── endpointslice.go │ │ │ │ └── interface.go │ │ │ ├── doc.go │ │ │ ├── events/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── event.go │ │ │ │ │ └── interface.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── event.go │ │ │ │ └── interface.go │ │ │ ├── extensions/ │ │ │ │ ├── interface.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── daemonset.go │ │ │ │ ├── deployment.go │ │ │ │ ├── ingress.go │ │ │ │ ├── interface.go │ │ │ │ ├── networkpolicy.go │ │ │ │ └── replicaset.go │ │ │ ├── factory.go │ │ │ ├── flowcontrol/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── flowschema.go │ │ │ │ │ ├── interface.go │ │ │ │ │ └── prioritylevelconfiguration.go │ │ │ │ ├── v1beta1/ │ │ │ │ │ ├── flowschema.go │ │ │ │ │ ├── interface.go │ │ │ │ │ └── prioritylevelconfiguration.go │ │ │ │ ├── v1beta2/ │ │ │ │ │ ├── flowschema.go │ │ │ │ │ ├── interface.go │ │ │ │ │ └── prioritylevelconfiguration.go │ │ │ │ └── v1beta3/ │ │ │ │ ├── flowschema.go │ │ │ │ ├── interface.go │ │ │ │ └── prioritylevelconfiguration.go │ │ │ ├── generic.go │ │ │ ├── internalinterfaces/ │ │ │ │ └── factory_interfaces.go │ │ │ ├── networking/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── ingress.go │ │ │ │ │ ├── ingressclass.go │ │ │ │ │ ├── interface.go │ │ │ │ │ └── networkpolicy.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── interface.go │ │ │ │ │ ├── ipaddress.go │ │ │ │ │ └── servicecidr.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── ingress.go │ │ │ │ ├── ingressclass.go │ │ │ │ ├── interface.go │ │ │ │ ├── ipaddress.go │ │ │ │ └── servicecidr.go │ │ │ ├── node/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── interface.go │ │ │ │ │ └── runtimeclass.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── interface.go │ │ │ │ │ └── runtimeclass.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── interface.go │ │ │ │ └── runtimeclass.go │ │ │ ├── policy/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── interface.go │ │ │ │ │ └── poddisruptionbudget.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── interface.go │ │ │ │ └── poddisruptionbudget.go │ │ │ ├── rbac/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── clusterrole.go │ │ │ │ │ ├── clusterrolebinding.go │ │ │ │ │ ├── interface.go │ │ │ │ │ ├── role.go │ │ │ │ │ └── rolebinding.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── clusterrole.go │ │ │ │ │ ├── clusterrolebinding.go │ │ │ │ │ ├── interface.go │ │ │ │ │ ├── role.go │ │ │ │ │ └── rolebinding.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── clusterrole.go │ │ │ │ ├── clusterrolebinding.go │ │ │ │ ├── interface.go │ │ │ │ ├── role.go │ │ │ │ └── rolebinding.go │ │ │ ├── resource/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1alpha3/ │ │ │ │ │ ├── deviceclass.go │ │ │ │ │ ├── interface.go │ │ │ │ │ ├── resourceclaim.go │ │ │ │ │ ├── resourceclaimtemplate.go │ │ │ │ │ └── resourceslice.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── deviceclass.go │ │ │ │ ├── interface.go │ │ │ │ ├── resourceclaim.go │ │ │ │ ├── resourceclaimtemplate.go │ │ │ │ └── resourceslice.go │ │ │ ├── scheduling/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── interface.go │ │ │ │ │ └── priorityclass.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── interface.go │ │ │ │ │ └── priorityclass.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── interface.go │ │ │ │ └── priorityclass.go │ │ │ ├── storage/ │ │ │ │ ├── interface.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── csidriver.go │ │ │ │ │ ├── csinode.go │ │ │ │ │ ├── csistoragecapacity.go │ │ │ │ │ ├── interface.go │ │ │ │ │ ├── storageclass.go │ │ │ │ │ └── volumeattachment.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── csistoragecapacity.go │ │ │ │ │ ├── interface.go │ │ │ │ │ ├── volumeattachment.go │ │ │ │ │ └── volumeattributesclass.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── csidriver.go │ │ │ │ ├── csinode.go │ │ │ │ ├── csistoragecapacity.go │ │ │ │ ├── interface.go │ │ │ │ ├── storageclass.go │ │ │ │ ├── volumeattachment.go │ │ │ │ └── volumeattributesclass.go │ │ │ └── storagemigration/ │ │ │ ├── interface.go │ │ │ └── v1alpha1/ │ │ │ ├── interface.go │ │ │ └── storageversionmigration.go │ │ ├── kubernetes/ │ │ │ ├── clientset.go │ │ │ ├── doc.go │ │ │ ├── fake/ │ │ │ │ ├── clientset_generated.go │ │ │ │ ├── doc.go │ │ │ │ └── register.go │ │ │ ├── import.go │ │ │ ├── scheme/ │ │ │ │ ├── doc.go │ │ │ │ └── register.go │ │ │ └── typed/ │ │ │ ├── admissionregistration/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── admissionregistration_client.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_admissionregistration_client.go │ │ │ │ │ │ ├── fake_mutatingwebhookconfiguration.go │ │ │ │ │ │ ├── fake_validatingadmissionpolicy.go │ │ │ │ │ │ ├── fake_validatingadmissionpolicybinding.go │ │ │ │ │ │ └── fake_validatingwebhookconfiguration.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── mutatingwebhookconfiguration.go │ │ │ │ │ ├── validatingadmissionpolicy.go │ │ │ │ │ ├── validatingadmissionpolicybinding.go │ │ │ │ │ └── validatingwebhookconfiguration.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── admissionregistration_client.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_admissionregistration_client.go │ │ │ │ │ │ ├── fake_mutatingadmissionpolicy.go │ │ │ │ │ │ ├── fake_mutatingadmissionpolicybinding.go │ │ │ │ │ │ ├── fake_validatingadmissionpolicy.go │ │ │ │ │ │ └── fake_validatingadmissionpolicybinding.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── mutatingadmissionpolicy.go │ │ │ │ │ ├── mutatingadmissionpolicybinding.go │ │ │ │ │ ├── validatingadmissionpolicy.go │ │ │ │ │ └── validatingadmissionpolicybinding.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── admissionregistration_client.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_admissionregistration_client.go │ │ │ │ │ ├── fake_mutatingwebhookconfiguration.go │ │ │ │ │ ├── fake_validatingadmissionpolicy.go │ │ │ │ │ ├── fake_validatingadmissionpolicybinding.go │ │ │ │ │ └── fake_validatingwebhookconfiguration.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── mutatingwebhookconfiguration.go │ │ │ │ ├── validatingadmissionpolicy.go │ │ │ │ ├── validatingadmissionpolicybinding.go │ │ │ │ └── validatingwebhookconfiguration.go │ │ │ ├── apiserverinternal/ │ │ │ │ └── v1alpha1/ │ │ │ │ ├── apiserverinternal_client.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_apiserverinternal_client.go │ │ │ │ │ └── fake_storageversion.go │ │ │ │ ├── generated_expansion.go │ │ │ │ └── storageversion.go │ │ │ ├── apps/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── apps_client.go │ │ │ │ │ ├── controllerrevision.go │ │ │ │ │ ├── daemonset.go │ │ │ │ │ ├── deployment.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_apps_client.go │ │ │ │ │ │ ├── fake_controllerrevision.go │ │ │ │ │ │ ├── fake_daemonset.go │ │ │ │ │ │ ├── fake_deployment.go │ │ │ │ │ │ ├── fake_replicaset.go │ │ │ │ │ │ └── fake_statefulset.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── replicaset.go │ │ │ │ │ └── statefulset.go │ │ │ │ ├── v1beta1/ │ │ │ │ │ ├── apps_client.go │ │ │ │ │ ├── controllerrevision.go │ │ │ │ │ ├── deployment.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_apps_client.go │ │ │ │ │ │ ├── fake_controllerrevision.go │ │ │ │ │ │ ├── fake_deployment.go │ │ │ │ │ │ └── fake_statefulset.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ └── statefulset.go │ │ │ │ └── v1beta2/ │ │ │ │ ├── apps_client.go │ │ │ │ ├── controllerrevision.go │ │ │ │ ├── daemonset.go │ │ │ │ ├── deployment.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_apps_client.go │ │ │ │ │ ├── fake_controllerrevision.go │ │ │ │ │ ├── fake_daemonset.go │ │ │ │ │ ├── fake_deployment.go │ │ │ │ │ ├── fake_replicaset.go │ │ │ │ │ └── fake_statefulset.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── replicaset.go │ │ │ │ └── statefulset.go │ │ │ ├── authentication/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── authentication_client.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_authentication_client.go │ │ │ │ │ │ ├── fake_selfsubjectreview.go │ │ │ │ │ │ └── fake_tokenreview.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── selfsubjectreview.go │ │ │ │ │ └── tokenreview.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── authentication_client.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_authentication_client.go │ │ │ │ │ │ └── fake_selfsubjectreview.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ └── selfsubjectreview.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── authentication_client.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_authentication_client.go │ │ │ │ │ ├── fake_selfsubjectreview.go │ │ │ │ │ └── fake_tokenreview.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── selfsubjectreview.go │ │ │ │ └── tokenreview.go │ │ │ ├── authorization/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── authorization_client.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_authorization_client.go │ │ │ │ │ │ ├── fake_localsubjectaccessreview.go │ │ │ │ │ │ ├── fake_selfsubjectaccessreview.go │ │ │ │ │ │ ├── fake_selfsubjectrulesreview.go │ │ │ │ │ │ └── fake_subjectaccessreview.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── localsubjectaccessreview.go │ │ │ │ │ ├── selfsubjectaccessreview.go │ │ │ │ │ ├── selfsubjectrulesreview.go │ │ │ │ │ └── subjectaccessreview.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── authorization_client.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_authorization_client.go │ │ │ │ │ ├── fake_localsubjectaccessreview.go │ │ │ │ │ ├── fake_selfsubjectaccessreview.go │ │ │ │ │ ├── fake_selfsubjectrulesreview.go │ │ │ │ │ └── fake_subjectaccessreview.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── localsubjectaccessreview.go │ │ │ │ ├── selfsubjectaccessreview.go │ │ │ │ ├── selfsubjectrulesreview.go │ │ │ │ └── subjectaccessreview.go │ │ │ ├── autoscaling/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── autoscaling_client.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_autoscaling_client.go │ │ │ │ │ │ └── fake_horizontalpodautoscaler.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ └── horizontalpodautoscaler.go │ │ │ │ ├── v2/ │ │ │ │ │ ├── autoscaling_client.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_autoscaling_client.go │ │ │ │ │ │ └── fake_horizontalpodautoscaler.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ └── horizontalpodautoscaler.go │ │ │ │ ├── v2beta1/ │ │ │ │ │ ├── autoscaling_client.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_autoscaling_client.go │ │ │ │ │ │ └── fake_horizontalpodautoscaler.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ └── horizontalpodautoscaler.go │ │ │ │ └── v2beta2/ │ │ │ │ ├── autoscaling_client.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_autoscaling_client.go │ │ │ │ │ └── fake_horizontalpodautoscaler.go │ │ │ │ ├── generated_expansion.go │ │ │ │ └── horizontalpodautoscaler.go │ │ │ ├── batch/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── batch_client.go │ │ │ │ │ ├── cronjob.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_batch_client.go │ │ │ │ │ │ ├── fake_cronjob.go │ │ │ │ │ │ └── fake_job.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ └── job.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── batch_client.go │ │ │ │ ├── cronjob.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_batch_client.go │ │ │ │ │ └── fake_cronjob.go │ │ │ │ └── generated_expansion.go │ │ │ ├── certificates/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── certificates_client.go │ │ │ │ │ ├── certificatesigningrequest.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_certificates_client.go │ │ │ │ │ │ └── fake_certificatesigningrequest.go │ │ │ │ │ └── generated_expansion.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── certificates_client.go │ │ │ │ │ ├── clustertrustbundle.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_certificates_client.go │ │ │ │ │ │ └── fake_clustertrustbundle.go │ │ │ │ │ └── generated_expansion.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── certificates_client.go │ │ │ │ ├── certificatesigningrequest.go │ │ │ │ ├── certificatesigningrequest_expansion.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_certificates_client.go │ │ │ │ │ ├── fake_certificatesigningrequest.go │ │ │ │ │ └── fake_certificatesigningrequest_expansion.go │ │ │ │ └── generated_expansion.go │ │ │ ├── coordination/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── coordination_client.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_coordination_client.go │ │ │ │ │ │ └── fake_lease.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ └── lease.go │ │ │ │ ├── v1alpha2/ │ │ │ │ │ ├── coordination_client.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_coordination_client.go │ │ │ │ │ │ └── fake_leasecandidate.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ └── leasecandidate.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── coordination_client.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_coordination_client.go │ │ │ │ │ └── fake_lease.go │ │ │ │ ├── generated_expansion.go │ │ │ │ └── lease.go │ │ │ ├── core/ │ │ │ │ └── v1/ │ │ │ │ ├── componentstatus.go │ │ │ │ ├── configmap.go │ │ │ │ ├── core_client.go │ │ │ │ ├── doc.go │ │ │ │ ├── endpoints.go │ │ │ │ ├── event.go │ │ │ │ ├── event_expansion.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_componentstatus.go │ │ │ │ │ ├── fake_configmap.go │ │ │ │ │ ├── fake_core_client.go │ │ │ │ │ ├── fake_endpoints.go │ │ │ │ │ ├── fake_event.go │ │ │ │ │ ├── fake_event_expansion.go │ │ │ │ │ ├── fake_limitrange.go │ │ │ │ │ ├── fake_namespace.go │ │ │ │ │ ├── fake_namespace_expansion.go │ │ │ │ │ ├── fake_node.go │ │ │ │ │ ├── fake_node_expansion.go │ │ │ │ │ ├── fake_persistentvolume.go │ │ │ │ │ ├── fake_persistentvolumeclaim.go │ │ │ │ │ ├── fake_pod.go │ │ │ │ │ ├── fake_pod_expansion.go │ │ │ │ │ ├── fake_podtemplate.go │ │ │ │ │ ├── fake_replicationcontroller.go │ │ │ │ │ ├── fake_resourcequota.go │ │ │ │ │ ├── fake_secret.go │ │ │ │ │ ├── fake_service.go │ │ │ │ │ ├── fake_service_expansion.go │ │ │ │ │ └── fake_serviceaccount.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── limitrange.go │ │ │ │ ├── namespace.go │ │ │ │ ├── namespace_expansion.go │ │ │ │ ├── node.go │ │ │ │ ├── node_expansion.go │ │ │ │ ├── persistentvolume.go │ │ │ │ ├── persistentvolumeclaim.go │ │ │ │ ├── pod.go │ │ │ │ ├── pod_expansion.go │ │ │ │ ├── podtemplate.go │ │ │ │ ├── replicationcontroller.go │ │ │ │ ├── resourcequota.go │ │ │ │ ├── secret.go │ │ │ │ ├── service.go │ │ │ │ ├── service_expansion.go │ │ │ │ └── serviceaccount.go │ │ │ ├── discovery/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── discovery_client.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── endpointslice.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_discovery_client.go │ │ │ │ │ │ └── fake_endpointslice.go │ │ │ │ │ └── generated_expansion.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── discovery_client.go │ │ │ │ ├── doc.go │ │ │ │ ├── endpointslice.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_discovery_client.go │ │ │ │ │ └── fake_endpointslice.go │ │ │ │ └── generated_expansion.go │ │ │ ├── events/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── event.go │ │ │ │ │ ├── events_client.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_event.go │ │ │ │ │ │ └── fake_events_client.go │ │ │ │ │ └── generated_expansion.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── doc.go │ │ │ │ ├── event.go │ │ │ │ ├── event_expansion.go │ │ │ │ ├── events_client.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_event.go │ │ │ │ │ ├── fake_event_expansion.go │ │ │ │ │ └── fake_events_client.go │ │ │ │ └── generated_expansion.go │ │ │ ├── extensions/ │ │ │ │ └── v1beta1/ │ │ │ │ ├── daemonset.go │ │ │ │ ├── deployment.go │ │ │ │ ├── deployment_expansion.go │ │ │ │ ├── doc.go │ │ │ │ ├── extensions_client.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_daemonset.go │ │ │ │ │ ├── fake_deployment.go │ │ │ │ │ ├── fake_deployment_expansion.go │ │ │ │ │ ├── fake_extensions_client.go │ │ │ │ │ ├── fake_ingress.go │ │ │ │ │ ├── fake_networkpolicy.go │ │ │ │ │ └── fake_replicaset.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── ingress.go │ │ │ │ ├── networkpolicy.go │ │ │ │ └── replicaset.go │ │ │ ├── flowcontrol/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_flowcontrol_client.go │ │ │ │ │ │ ├── fake_flowschema.go │ │ │ │ │ │ └── fake_prioritylevelconfiguration.go │ │ │ │ │ ├── flowcontrol_client.go │ │ │ │ │ ├── flowschema.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ └── prioritylevelconfiguration.go │ │ │ │ ├── v1beta1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_flowcontrol_client.go │ │ │ │ │ │ ├── fake_flowschema.go │ │ │ │ │ │ └── fake_prioritylevelconfiguration.go │ │ │ │ │ ├── flowcontrol_client.go │ │ │ │ │ ├── flowschema.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ └── prioritylevelconfiguration.go │ │ │ │ ├── v1beta2/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_flowcontrol_client.go │ │ │ │ │ │ ├── fake_flowschema.go │ │ │ │ │ │ └── fake_prioritylevelconfiguration.go │ │ │ │ │ ├── flowcontrol_client.go │ │ │ │ │ ├── flowschema.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ └── prioritylevelconfiguration.go │ │ │ │ └── v1beta3/ │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_flowcontrol_client.go │ │ │ │ │ ├── fake_flowschema.go │ │ │ │ │ └── fake_prioritylevelconfiguration.go │ │ │ │ ├── flowcontrol_client.go │ │ │ │ ├── flowschema.go │ │ │ │ ├── generated_expansion.go │ │ │ │ └── prioritylevelconfiguration.go │ │ │ ├── networking/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_ingress.go │ │ │ │ │ │ ├── fake_ingressclass.go │ │ │ │ │ │ ├── fake_networking_client.go │ │ │ │ │ │ └── fake_networkpolicy.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── ingress.go │ │ │ │ │ ├── ingressclass.go │ │ │ │ │ ├── networking_client.go │ │ │ │ │ └── networkpolicy.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_ipaddress.go │ │ │ │ │ │ ├── fake_networking_client.go │ │ │ │ │ │ └── fake_servicecidr.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── ipaddress.go │ │ │ │ │ ├── networking_client.go │ │ │ │ │ └── servicecidr.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_ingress.go │ │ │ │ │ ├── fake_ingressclass.go │ │ │ │ │ ├── fake_ipaddress.go │ │ │ │ │ ├── fake_networking_client.go │ │ │ │ │ └── fake_servicecidr.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── ingress.go │ │ │ │ ├── ingressclass.go │ │ │ │ ├── ipaddress.go │ │ │ │ ├── networking_client.go │ │ │ │ └── servicecidr.go │ │ │ ├── node/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_node_client.go │ │ │ │ │ │ └── fake_runtimeclass.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── node_client.go │ │ │ │ │ └── runtimeclass.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_node_client.go │ │ │ │ │ │ └── fake_runtimeclass.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── node_client.go │ │ │ │ │ └── runtimeclass.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_node_client.go │ │ │ │ │ └── fake_runtimeclass.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── node_client.go │ │ │ │ └── runtimeclass.go │ │ │ ├── policy/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── eviction.go │ │ │ │ │ ├── eviction_expansion.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_eviction.go │ │ │ │ │ │ ├── fake_eviction_expansion.go │ │ │ │ │ │ ├── fake_poddisruptionbudget.go │ │ │ │ │ │ └── fake_policy_client.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── poddisruptionbudget.go │ │ │ │ │ └── policy_client.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── doc.go │ │ │ │ ├── eviction.go │ │ │ │ ├── eviction_expansion.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_eviction.go │ │ │ │ │ ├── fake_eviction_expansion.go │ │ │ │ │ ├── fake_poddisruptionbudget.go │ │ │ │ │ └── fake_policy_client.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── poddisruptionbudget.go │ │ │ │ └── policy_client.go │ │ │ ├── rbac/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── clusterrole.go │ │ │ │ │ ├── clusterrolebinding.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_clusterrole.go │ │ │ │ │ │ ├── fake_clusterrolebinding.go │ │ │ │ │ │ ├── fake_rbac_client.go │ │ │ │ │ │ ├── fake_role.go │ │ │ │ │ │ └── fake_rolebinding.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── rbac_client.go │ │ │ │ │ ├── role.go │ │ │ │ │ └── rolebinding.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── clusterrole.go │ │ │ │ │ ├── clusterrolebinding.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_clusterrole.go │ │ │ │ │ │ ├── fake_clusterrolebinding.go │ │ │ │ │ │ ├── fake_rbac_client.go │ │ │ │ │ │ ├── fake_role.go │ │ │ │ │ │ └── fake_rolebinding.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── rbac_client.go │ │ │ │ │ ├── role.go │ │ │ │ │ └── rolebinding.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── clusterrole.go │ │ │ │ ├── clusterrolebinding.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_clusterrole.go │ │ │ │ │ ├── fake_clusterrolebinding.go │ │ │ │ │ ├── fake_rbac_client.go │ │ │ │ │ ├── fake_role.go │ │ │ │ │ └── fake_rolebinding.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── rbac_client.go │ │ │ │ ├── role.go │ │ │ │ └── rolebinding.go │ │ │ ├── resource/ │ │ │ │ ├── v1alpha3/ │ │ │ │ │ ├── deviceclass.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_deviceclass.go │ │ │ │ │ │ ├── fake_resource_client.go │ │ │ │ │ │ ├── fake_resourceclaim.go │ │ │ │ │ │ ├── fake_resourceclaimtemplate.go │ │ │ │ │ │ └── fake_resourceslice.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── resource_client.go │ │ │ │ │ ├── resourceclaim.go │ │ │ │ │ ├── resourceclaimtemplate.go │ │ │ │ │ └── resourceslice.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── deviceclass.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_deviceclass.go │ │ │ │ │ ├── fake_resource_client.go │ │ │ │ │ ├── fake_resourceclaim.go │ │ │ │ │ ├── fake_resourceclaimtemplate.go │ │ │ │ │ └── fake_resourceslice.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── resource_client.go │ │ │ │ ├── resourceclaim.go │ │ │ │ ├── resourceclaimtemplate.go │ │ │ │ └── resourceslice.go │ │ │ ├── scheduling/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_priorityclass.go │ │ │ │ │ │ └── fake_scheduling_client.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── priorityclass.go │ │ │ │ │ └── scheduling_client.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_priorityclass.go │ │ │ │ │ │ └── fake_scheduling_client.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── priorityclass.go │ │ │ │ │ └── scheduling_client.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_priorityclass.go │ │ │ │ │ └── fake_scheduling_client.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── priorityclass.go │ │ │ │ └── scheduling_client.go │ │ │ ├── storage/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── csidriver.go │ │ │ │ │ ├── csinode.go │ │ │ │ │ ├── csistoragecapacity.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_csidriver.go │ │ │ │ │ │ ├── fake_csinode.go │ │ │ │ │ │ ├── fake_csistoragecapacity.go │ │ │ │ │ │ ├── fake_storage_client.go │ │ │ │ │ │ ├── fake_storageclass.go │ │ │ │ │ │ └── fake_volumeattachment.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── storage_client.go │ │ │ │ │ ├── storageclass.go │ │ │ │ │ └── volumeattachment.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── csistoragecapacity.go │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake/ │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── fake_csistoragecapacity.go │ │ │ │ │ │ ├── fake_storage_client.go │ │ │ │ │ │ ├── fake_volumeattachment.go │ │ │ │ │ │ └── fake_volumeattributesclass.go │ │ │ │ │ ├── generated_expansion.go │ │ │ │ │ ├── storage_client.go │ │ │ │ │ ├── volumeattachment.go │ │ │ │ │ └── volumeattributesclass.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── csidriver.go │ │ │ │ ├── csinode.go │ │ │ │ ├── csistoragecapacity.go │ │ │ │ ├── doc.go │ │ │ │ ├── fake/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── fake_csidriver.go │ │ │ │ │ ├── fake_csinode.go │ │ │ │ │ ├── fake_csistoragecapacity.go │ │ │ │ │ ├── fake_storage_client.go │ │ │ │ │ ├── fake_storageclass.go │ │ │ │ │ ├── fake_volumeattachment.go │ │ │ │ │ └── fake_volumeattributesclass.go │ │ │ │ ├── generated_expansion.go │ │ │ │ ├── storage_client.go │ │ │ │ ├── storageclass.go │ │ │ │ ├── volumeattachment.go │ │ │ │ └── volumeattributesclass.go │ │ │ └── storagemigration/ │ │ │ └── v1alpha1/ │ │ │ ├── doc.go │ │ │ ├── fake/ │ │ │ │ ├── doc.go │ │ │ │ ├── fake_storagemigration_client.go │ │ │ │ └── fake_storageversionmigration.go │ │ │ ├── generated_expansion.go │ │ │ ├── storagemigration_client.go │ │ │ └── storageversionmigration.go │ │ ├── listers/ │ │ │ ├── admissionregistration/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── mutatingwebhookconfiguration.go │ │ │ │ │ ├── validatingadmissionpolicy.go │ │ │ │ │ ├── validatingadmissionpolicybinding.go │ │ │ │ │ └── validatingwebhookconfiguration.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── mutatingadmissionpolicy.go │ │ │ │ │ ├── mutatingadmissionpolicybinding.go │ │ │ │ │ ├── validatingadmissionpolicy.go │ │ │ │ │ └── validatingadmissionpolicybinding.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── expansion_generated.go │ │ │ │ ├── mutatingwebhookconfiguration.go │ │ │ │ ├── validatingadmissionpolicy.go │ │ │ │ ├── validatingadmissionpolicybinding.go │ │ │ │ └── validatingwebhookconfiguration.go │ │ │ ├── apiserverinternal/ │ │ │ │ └── v1alpha1/ │ │ │ │ ├── expansion_generated.go │ │ │ │ └── storageversion.go │ │ │ ├── apps/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── controllerrevision.go │ │ │ │ │ ├── daemonset.go │ │ │ │ │ ├── daemonset_expansion.go │ │ │ │ │ ├── deployment.go │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── replicaset.go │ │ │ │ │ ├── replicaset_expansion.go │ │ │ │ │ ├── statefulset.go │ │ │ │ │ └── statefulset_expansion.go │ │ │ │ ├── v1beta1/ │ │ │ │ │ ├── controllerrevision.go │ │ │ │ │ ├── deployment.go │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── statefulset.go │ │ │ │ │ └── statefulset_expansion.go │ │ │ │ └── v1beta2/ │ │ │ │ ├── controllerrevision.go │ │ │ │ ├── daemonset.go │ │ │ │ ├── daemonset_expansion.go │ │ │ │ ├── deployment.go │ │ │ │ ├── expansion_generated.go │ │ │ │ ├── replicaset.go │ │ │ │ ├── replicaset_expansion.go │ │ │ │ ├── statefulset.go │ │ │ │ └── statefulset_expansion.go │ │ │ ├── autoscaling/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ └── horizontalpodautoscaler.go │ │ │ │ ├── v2/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ └── horizontalpodautoscaler.go │ │ │ │ ├── v2beta1/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ └── horizontalpodautoscaler.go │ │ │ │ └── v2beta2/ │ │ │ │ ├── expansion_generated.go │ │ │ │ └── horizontalpodautoscaler.go │ │ │ ├── batch/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── cronjob.go │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── job.go │ │ │ │ │ └── job_expansion.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── cronjob.go │ │ │ │ └── expansion_generated.go │ │ │ ├── certificates/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── certificatesigningrequest.go │ │ │ │ │ └── expansion_generated.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── clustertrustbundle.go │ │ │ │ │ └── expansion_generated.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── certificatesigningrequest.go │ │ │ │ └── expansion_generated.go │ │ │ ├── coordination/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ └── lease.go │ │ │ │ ├── v1alpha2/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ └── leasecandidate.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── expansion_generated.go │ │ │ │ └── lease.go │ │ │ ├── core/ │ │ │ │ └── v1/ │ │ │ │ ├── componentstatus.go │ │ │ │ ├── configmap.go │ │ │ │ ├── endpoints.go │ │ │ │ ├── event.go │ │ │ │ ├── expansion_generated.go │ │ │ │ ├── limitrange.go │ │ │ │ ├── namespace.go │ │ │ │ ├── node.go │ │ │ │ ├── persistentvolume.go │ │ │ │ ├── persistentvolumeclaim.go │ │ │ │ ├── pod.go │ │ │ │ ├── podtemplate.go │ │ │ │ ├── replicationcontroller.go │ │ │ │ ├── replicationcontroller_expansion.go │ │ │ │ ├── resourcequota.go │ │ │ │ ├── secret.go │ │ │ │ ├── service.go │ │ │ │ └── serviceaccount.go │ │ │ ├── discovery/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── endpointslice.go │ │ │ │ │ └── expansion_generated.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── endpointslice.go │ │ │ │ └── expansion_generated.go │ │ │ ├── doc.go │ │ │ ├── events/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── event.go │ │ │ │ │ └── expansion_generated.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── event.go │ │ │ │ └── expansion_generated.go │ │ │ ├── extensions/ │ │ │ │ └── v1beta1/ │ │ │ │ ├── daemonset.go │ │ │ │ ├── daemonset_expansion.go │ │ │ │ ├── deployment.go │ │ │ │ ├── expansion_generated.go │ │ │ │ ├── ingress.go │ │ │ │ ├── networkpolicy.go │ │ │ │ ├── replicaset.go │ │ │ │ └── replicaset_expansion.go │ │ │ ├── flowcontrol/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── flowschema.go │ │ │ │ │ └── prioritylevelconfiguration.go │ │ │ │ ├── v1beta1/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── flowschema.go │ │ │ │ │ └── prioritylevelconfiguration.go │ │ │ │ ├── v1beta2/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── flowschema.go │ │ │ │ │ └── prioritylevelconfiguration.go │ │ │ │ └── v1beta3/ │ │ │ │ ├── expansion_generated.go │ │ │ │ ├── flowschema.go │ │ │ │ └── prioritylevelconfiguration.go │ │ │ ├── generic_helpers.go │ │ │ ├── networking/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── ingress.go │ │ │ │ │ ├── ingressclass.go │ │ │ │ │ └── networkpolicy.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── ipaddress.go │ │ │ │ │ └── servicecidr.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── expansion_generated.go │ │ │ │ ├── ingress.go │ │ │ │ ├── ingressclass.go │ │ │ │ ├── ipaddress.go │ │ │ │ └── servicecidr.go │ │ │ ├── node/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ └── runtimeclass.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ └── runtimeclass.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── expansion_generated.go │ │ │ │ └── runtimeclass.go │ │ │ ├── policy/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── eviction.go │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── poddisruptionbudget.go │ │ │ │ │ └── poddisruptionbudget_expansion.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── eviction.go │ │ │ │ ├── expansion_generated.go │ │ │ │ ├── poddisruptionbudget.go │ │ │ │ └── poddisruptionbudget_expansion.go │ │ │ ├── rbac/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── clusterrole.go │ │ │ │ │ ├── clusterrolebinding.go │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── role.go │ │ │ │ │ └── rolebinding.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── clusterrole.go │ │ │ │ │ ├── clusterrolebinding.go │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── role.go │ │ │ │ │ └── rolebinding.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── clusterrole.go │ │ │ │ ├── clusterrolebinding.go │ │ │ │ ├── expansion_generated.go │ │ │ │ ├── role.go │ │ │ │ └── rolebinding.go │ │ │ ├── resource/ │ │ │ │ ├── v1alpha3/ │ │ │ │ │ ├── deviceclass.go │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── resourceclaim.go │ │ │ │ │ ├── resourceclaimtemplate.go │ │ │ │ │ └── resourceslice.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── deviceclass.go │ │ │ │ ├── expansion_generated.go │ │ │ │ ├── resourceclaim.go │ │ │ │ ├── resourceclaimtemplate.go │ │ │ │ └── resourceslice.go │ │ │ ├── scheduling/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ └── priorityclass.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ └── priorityclass.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── expansion_generated.go │ │ │ │ └── priorityclass.go │ │ │ ├── storage/ │ │ │ │ ├── v1/ │ │ │ │ │ ├── csidriver.go │ │ │ │ │ ├── csinode.go │ │ │ │ │ ├── csistoragecapacity.go │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── storageclass.go │ │ │ │ │ └── volumeattachment.go │ │ │ │ ├── v1alpha1/ │ │ │ │ │ ├── csistoragecapacity.go │ │ │ │ │ ├── expansion_generated.go │ │ │ │ │ ├── volumeattachment.go │ │ │ │ │ └── volumeattributesclass.go │ │ │ │ └── v1beta1/ │ │ │ │ ├── csidriver.go │ │ │ │ ├── csinode.go │ │ │ │ ├── csistoragecapacity.go │ │ │ │ ├── expansion_generated.go │ │ │ │ ├── storageclass.go │ │ │ │ ├── volumeattachment.go │ │ │ │ └── volumeattributesclass.go │ │ │ └── storagemigration/ │ │ │ └── v1alpha1/ │ │ │ ├── expansion_generated.go │ │ │ └── storageversionmigration.go │ │ ├── metadata/ │ │ │ ├── interface.go │ │ │ └── metadata.go │ │ ├── openapi/ │ │ │ ├── OWNERS │ │ │ ├── client.go │ │ │ ├── groupversion.go │ │ │ └── typeconverter.go │ │ ├── pkg/ │ │ │ ├── apis/ │ │ │ │ └── clientauthentication/ │ │ │ │ ├── OWNERS │ │ │ │ ├── doc.go │ │ │ │ ├── install/ │ │ │ │ │ └── install.go │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── v1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── register.go │ │ │ │ │ ├── types.go │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ ├── v1beta1/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── register.go │ │ │ │ │ ├── types.go │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ └── zz_generated.deepcopy.go │ │ │ └── version/ │ │ │ ├── base.go │ │ │ ├── doc.go │ │ │ └── version.go │ │ ├── plugin/ │ │ │ └── pkg/ │ │ │ └── client/ │ │ │ └── auth/ │ │ │ └── exec/ │ │ │ ├── exec.go │ │ │ └── metrics.go │ │ ├── rest/ │ │ │ ├── OWNERS │ │ │ ├── client.go │ │ │ ├── config.go │ │ │ ├── exec.go │ │ │ ├── fake/ │ │ │ │ └── fake.go │ │ │ ├── plugin.go │ │ │ ├── request.go │ │ │ ├── transport.go │ │ │ ├── url_utils.go │ │ │ ├── urlbackoff.go │ │ │ ├── warnings.go │ │ │ ├── watch/ │ │ │ │ ├── decoder.go │ │ │ │ └── encoder.go │ │ │ ├── with_retry.go │ │ │ └── zz_generated.deepcopy.go │ │ ├── restmapper/ │ │ │ ├── category_expansion.go │ │ │ ├── discovery.go │ │ │ └── shortcut.go │ │ ├── testing/ │ │ │ ├── actions.go │ │ │ ├── fake.go │ │ │ ├── fixture.go │ │ │ └── interface.go │ │ ├── third_party/ │ │ │ └── forked/ │ │ │ └── golang/ │ │ │ ├── LICENSE │ │ │ ├── PATENTS │ │ │ └── template/ │ │ │ ├── exec.go │ │ │ └── funcs.go │ │ ├── tools/ │ │ │ ├── auth/ │ │ │ │ ├── OWNERS │ │ │ │ └── clientauth.go │ │ │ ├── cache/ │ │ │ │ ├── OWNERS │ │ │ │ ├── controller.go │ │ │ │ ├── delta_fifo.go │ │ │ │ ├── doc.go │ │ │ │ ├── expiration_cache.go │ │ │ │ ├── expiration_cache_fakes.go │ │ │ │ ├── fake_custom_store.go │ │ │ │ ├── fifo.go │ │ │ │ ├── heap.go │ │ │ │ ├── index.go │ │ │ │ ├── listers.go │ │ │ │ ├── listwatch.go │ │ │ │ ├── mutation_cache.go │ │ │ │ ├── mutation_detector.go │ │ │ │ ├── object-names.go │ │ │ │ ├── reflector.go │ │ │ │ ├── reflector_data_consistency_detector.go │ │ │ │ ├── reflector_metrics.go │ │ │ │ ├── retry_with_deadline.go │ │ │ │ ├── shared_informer.go │ │ │ │ ├── store.go │ │ │ │ ├── synctrack/ │ │ │ │ │ ├── lazy.go │ │ │ │ │ └── synctrack.go │ │ │ │ ├── thread_safe_store.go │ │ │ │ └── undelta_store.go │ │ │ ├── clientcmd/ │ │ │ │ ├── api/ │ │ │ │ │ ├── doc.go │ │ │ │ │ ├── helpers.go │ │ │ │ │ ├── latest/ │ │ │ │ │ │ └── latest.go │ │ │ │ │ ├── register.go │ │ │ │ │ ├── types.go │ │ │ │ │ ├── v1/ │ │ │ │ │ │ ├── conversion.go │ │ │ │ │ │ ├── defaults.go │ │ │ │ │ │ ├── doc.go │ │ │ │ │ │ ├── register.go │ │ │ │ │ │ ├── types.go │ │ │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ │ │ └── zz_generated.defaults.go │ │ │ │ │ └── zz_generated.deepcopy.go │ │ │ │ ├── auth_loaders.go │ │ │ │ ├── client_config.go │ │ │ │ ├── config.go │ │ │ │ ├── doc.go │ │ │ │ ├── flag.go │ │ │ │ ├── helpers.go │ │ │ │ ├── loader.go │ │ │ │ ├── merge.go │ │ │ │ ├── merged_client_builder.go │ │ │ │ ├── overrides.go │ │ │ │ └── validation.go │ │ │ ├── events/ │ │ │ │ ├── OWNERS │ │ │ │ ├── doc.go │ │ │ │ ├── event_broadcaster.go │ │ │ │ ├── event_recorder.go │ │ │ │ ├── fake.go │ │ │ │ ├── helper.go │ │ │ │ └── interfaces.go │ │ │ ├── internal/ │ │ │ │ └── events/ │ │ │ │ └── interfaces.go │ │ │ ├── leaderelection/ │ │ │ │ ├── OWNERS │ │ │ │ ├── healthzadaptor.go │ │ │ │ ├── leaderelection.go │ │ │ │ ├── leasecandidate.go │ │ │ │ ├── metrics.go │ │ │ │ └── resourcelock/ │ │ │ │ ├── interface.go │ │ │ │ ├── leaselock.go │ │ │ │ └── multilock.go │ │ │ ├── metrics/ │ │ │ │ ├── OWNERS │ │ │ │ └── metrics.go │ │ │ ├── pager/ │ │ │ │ └── pager.go │ │ │ ├── record/ │ │ │ │ ├── OWNERS │ │ │ │ ├── doc.go │ │ │ │ ├── event.go │ │ │ │ ├── events_cache.go │ │ │ │ ├── fake.go │ │ │ │ └── util/ │ │ │ │ └── util.go │ │ │ ├── reference/ │ │ │ │ └── ref.go │ │ │ └── remotecommand/ │ │ │ ├── OWNERS │ │ │ ├── doc.go │ │ │ ├── errorstream.go │ │ │ ├── fallback.go │ │ │ ├── reader.go │ │ │ ├── remotecommand.go │ │ │ ├── resize.go │ │ │ ├── spdy.go │ │ │ ├── v1.go │ │ │ ├── v2.go │ │ │ ├── v3.go │ │ │ ├── v4.go │ │ │ ├── v5.go │ │ │ └── websocket.go │ │ ├── transport/ │ │ │ ├── OWNERS │ │ │ ├── cache.go │ │ │ ├── cache_go118.go │ │ │ ├── cert_rotation.go │ │ │ ├── config.go │ │ │ ├── round_trippers.go │ │ │ ├── spdy/ │ │ │ │ └── spdy.go │ │ │ ├── token_source.go │ │ │ ├── transport.go │ │ │ └── websocket/ │ │ │ └── roundtripper.go │ │ └── util/ │ │ ├── apply/ │ │ │ └── apply.go │ │ ├── cert/ │ │ │ ├── OWNERS │ │ │ ├── cert.go │ │ │ ├── csr.go │ │ │ ├── io.go │ │ │ ├── pem.go │ │ │ └── server_inspection.go │ │ ├── connrotation/ │ │ │ └── connrotation.go │ │ ├── consistencydetector/ │ │ │ ├── data_consistency_detector.go │ │ │ ├── list_data_consistency_detector.go │ │ │ └── watch_list_data_consistency_detector.go │ │ ├── exec/ │ │ │ └── exec.go │ │ ├── flowcontrol/ │ │ │ ├── backoff.go │ │ │ └── throttle.go │ │ ├── homedir/ │ │ │ └── homedir.go │ │ ├── jsonpath/ │ │ │ ├── doc.go │ │ │ ├── jsonpath.go │ │ │ ├── node.go │ │ │ └── parser.go │ │ ├── keyutil/ │ │ │ ├── OWNERS │ │ │ └── key.go │ │ ├── retry/ │ │ │ ├── OWNERS │ │ │ └── util.go │ │ ├── watchlist/ │ │ │ └── watch_list.go │ │ └── workqueue/ │ │ ├── default_rate_limiters.go │ │ ├── delaying_queue.go │ │ ├── doc.go │ │ ├── metrics.go │ │ ├── parallelizer.go │ │ ├── queue.go │ │ └── rate_limiting_queue.go │ ├── component-base/ │ │ ├── LICENSE │ │ ├── cli/ │ │ │ └── flag/ │ │ │ ├── ciphersuites_flag.go │ │ │ ├── colon_separated_multimap_string_string.go │ │ │ ├── configuration_map.go │ │ │ ├── flags.go │ │ │ ├── langle_separated_map_string_string.go │ │ │ ├── map_string_bool.go │ │ │ ├── map_string_string.go │ │ │ ├── namedcertkey_flag.go │ │ │ ├── noop.go │ │ │ ├── omitempty.go │ │ │ ├── sectioned.go │ │ │ ├── string_flag.go │ │ │ ├── string_slice_flag.go │ │ │ └── tristate.go │ │ ├── featuregate/ │ │ │ ├── OWNERS │ │ │ ├── feature_gate.go │ │ │ └── registry.go │ │ ├── logs/ │ │ │ ├── OWNERS │ │ │ ├── api/ │ │ │ │ └── v1/ │ │ │ │ ├── doc.go │ │ │ │ ├── kube_features.go │ │ │ │ ├── options.go │ │ │ │ ├── options_no_slog.go │ │ │ │ ├── options_slog.go │ │ │ │ ├── pflags.go │ │ │ │ ├── registry.go │ │ │ │ ├── text.go │ │ │ │ ├── types.go │ │ │ │ └── zz_generated.deepcopy.go │ │ │ ├── internal/ │ │ │ │ └── setverbositylevel/ │ │ │ │ └── setverbositylevel.go │ │ │ ├── klogflags/ │ │ │ │ └── klogflags.go │ │ │ └── logs.go │ │ ├── metrics/ │ │ │ ├── OWNERS │ │ │ ├── buckets.go │ │ │ ├── collector.go │ │ │ ├── counter.go │ │ │ ├── desc.go │ │ │ ├── features/ │ │ │ │ └── kube_features.go │ │ │ ├── gauge.go │ │ │ ├── histogram.go │ │ │ ├── http.go │ │ │ ├── labels.go │ │ │ ├── legacyregistry/ │ │ │ │ └── registry.go │ │ │ ├── metric.go │ │ │ ├── options.go │ │ │ ├── opts.go │ │ │ ├── processstarttime.go │ │ │ ├── processstarttime_others.go │ │ │ ├── processstarttime_windows.go │ │ │ ├── prometheus/ │ │ │ │ ├── feature/ │ │ │ │ │ └── metrics.go │ │ │ │ ├── slis/ │ │ │ │ │ ├── metrics.go │ │ │ │ │ ├── registry.go │ │ │ │ │ └── routes.go │ │ │ │ └── workqueue/ │ │ │ │ └── metrics.go │ │ │ ├── prometheusextension/ │ │ │ │ ├── timing_histogram.go │ │ │ │ ├── timing_histogram_vec.go │ │ │ │ ├── weighted_histogram.go │ │ │ │ └── weighted_histogram_vec.go │ │ │ ├── registry.go │ │ │ ├── summary.go │ │ │ ├── testutil/ │ │ │ │ ├── metrics.go │ │ │ │ ├── promlint.go │ │ │ │ └── testutil.go │ │ │ ├── timing_histogram.go │ │ │ ├── value.go │ │ │ ├── version.go │ │ │ ├── version_parser.go │ │ │ └── wrappers.go │ │ ├── tracing/ │ │ │ ├── OWNERS │ │ │ ├── api/ │ │ │ │ └── v1/ │ │ │ │ ├── config.go │ │ │ │ ├── doc.go │ │ │ │ ├── types.go │ │ │ │ └── zz_generated.deepcopy.go │ │ │ ├── tracing.go │ │ │ └── utils.go │ │ ├── version/ │ │ │ ├── OWNERS │ │ │ ├── base.go │ │ │ ├── dynamic.go │ │ │ └── version.go │ │ └── zpages/ │ │ └── flagz/ │ │ ├── flagreader.go │ │ └── flagz.go │ ├── klog/ │ │ └── v2/ │ │ ├── .gitignore │ │ ├── .golangci.yaml │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── OWNERS │ │ ├── README.md │ │ ├── RELEASE.md │ │ ├── SECURITY.md │ │ ├── SECURITY_CONTACTS │ │ ├── code-of-conduct.md │ │ ├── contextual.go │ │ ├── contextual_slog.go │ │ ├── exit.go │ │ ├── format.go │ │ ├── imports.go │ │ ├── internal/ │ │ │ ├── buffer/ │ │ │ │ └── buffer.go │ │ │ ├── clock/ │ │ │ │ ├── README.md │ │ │ │ └── clock.go │ │ │ ├── dbg/ │ │ │ │ └── dbg.go │ │ │ ├── serialize/ │ │ │ │ ├── keyvalues.go │ │ │ │ ├── keyvalues_no_slog.go │ │ │ │ └── keyvalues_slog.go │ │ │ ├── severity/ │ │ │ │ └── severity.go │ │ │ ├── sloghandler/ │ │ │ │ └── sloghandler_slog.go │ │ │ └── verbosity/ │ │ │ └── verbosity.go │ │ ├── k8s_references.go │ │ ├── k8s_references_slog.go │ │ ├── klog.go │ │ ├── klog_file.go │ │ ├── klog_file_others.go │ │ ├── klog_file_windows.go │ │ ├── klogr.go │ │ ├── klogr_slog.go │ │ ├── safeptr.go │ │ └── textlogger/ │ │ ├── options.go │ │ ├── textlogger.go │ │ └── textlogger_slog.go │ ├── kube-aggregator/ │ │ ├── LICENSE │ │ └── pkg/ │ │ ├── apis/ │ │ │ └── apiregistration/ │ │ │ ├── doc.go │ │ │ ├── helpers.go │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── v1/ │ │ │ │ ├── defaults.go │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ ├── zz_generated.defaults.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ ├── v1beta1/ │ │ │ │ ├── defaults.go │ │ │ │ ├── doc.go │ │ │ │ ├── generated.pb.go │ │ │ │ ├── generated.proto │ │ │ │ ├── register.go │ │ │ │ ├── types.go │ │ │ │ ├── zz_generated.conversion.go │ │ │ │ ├── zz_generated.deepcopy.go │ │ │ │ ├── zz_generated.defaults.go │ │ │ │ └── zz_generated.prerelease-lifecycle.go │ │ │ └── zz_generated.deepcopy.go │ │ └── client/ │ │ └── clientset_generated/ │ │ └── clientset/ │ │ ├── clientset.go │ │ ├── scheme/ │ │ │ ├── doc.go │ │ │ └── register.go │ │ └── typed/ │ │ └── apiregistration/ │ │ ├── v1/ │ │ │ ├── apiregistration_client.go │ │ │ ├── apiservice.go │ │ │ ├── doc.go │ │ │ └── generated_expansion.go │ │ └── v1beta1/ │ │ ├── apiregistration_client.go │ │ ├── apiservice.go │ │ ├── doc.go │ │ └── generated_expansion.go │ ├── kube-openapi/ │ │ ├── LICENSE │ │ └── pkg/ │ │ ├── builder/ │ │ │ ├── doc.go │ │ │ ├── openapi.go │ │ │ ├── parameters.go │ │ │ └── util.go │ │ ├── builder3/ │ │ │ ├── openapi.go │ │ │ ├── util/ │ │ │ │ └── util.go │ │ │ └── util.go │ │ ├── cached/ │ │ │ └── cache.go │ │ ├── common/ │ │ │ ├── common.go │ │ │ ├── doc.go │ │ │ ├── interfaces.go │ │ │ └── restfuladapter/ │ │ │ ├── adapter.go │ │ │ ├── param_adapter.go │ │ │ ├── response_error_adapter.go │ │ │ ├── route_adapter.go │ │ │ └── webservice_adapter.go │ │ ├── handler/ │ │ │ ├── default_pruning.go │ │ │ └── handler.go │ │ ├── handler3/ │ │ │ └── handler.go │ │ ├── internal/ │ │ │ ├── flags.go │ │ │ ├── serialization.go │ │ │ └── third_party/ │ │ │ └── go-json-experiment/ │ │ │ └── json/ │ │ │ ├── AUTHORS │ │ │ ├── CONTRIBUTORS │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── arshal.go │ │ │ ├── arshal_any.go │ │ │ ├── arshal_default.go │ │ │ ├── arshal_funcs.go │ │ │ ├── arshal_inlined.go │ │ │ ├── arshal_methods.go │ │ │ ├── arshal_time.go │ │ │ ├── decode.go │ │ │ ├── doc.go │ │ │ ├── encode.go │ │ │ ├── errors.go │ │ │ ├── fields.go │ │ │ ├── fold.go │ │ │ ├── intern.go │ │ │ ├── pools.go │ │ │ ├── state.go │ │ │ ├── token.go │ │ │ └── value.go │ │ ├── schemaconv/ │ │ │ ├── openapi.go │ │ │ ├── proto_models.go │ │ │ └── smd.go │ │ ├── schemamutation/ │ │ │ └── walker.go │ │ ├── spec3/ │ │ │ ├── component.go │ │ │ ├── encoding.go │ │ │ ├── example.go │ │ │ ├── external_documentation.go │ │ │ ├── fuzz.go │ │ │ ├── header.go │ │ │ ├── media_type.go │ │ │ ├── operation.go │ │ │ ├── parameter.go │ │ │ ├── path.go │ │ │ ├── request_body.go │ │ │ ├── response.go │ │ │ ├── security_scheme.go │ │ │ ├── server.go │ │ │ └── spec.go │ │ ├── util/ │ │ │ ├── proto/ │ │ │ │ ├── OWNERS │ │ │ │ ├── doc.go │ │ │ │ ├── document.go │ │ │ │ ├── document_v3.go │ │ │ │ └── openapi.go │ │ │ ├── trie.go │ │ │ └── util.go │ │ └── validation/ │ │ ├── errors/ │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── api.go │ │ │ ├── doc.go │ │ │ ├── headers.go │ │ │ └── schema.go │ │ ├── spec/ │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── contact_info.go │ │ │ ├── external_docs.go │ │ │ ├── gnostic.go │ │ │ ├── header.go │ │ │ ├── info.go │ │ │ ├── items.go │ │ │ ├── license.go │ │ │ ├── operation.go │ │ │ ├── parameter.go │ │ │ ├── path_item.go │ │ │ ├── paths.go │ │ │ ├── ref.go │ │ │ ├── response.go │ │ │ ├── responses.go │ │ │ ├── schema.go │ │ │ ├── security_scheme.go │ │ │ ├── swagger.go │ │ │ └── tag.go │ │ └── strfmt/ │ │ ├── .gitignore │ │ ├── LICENSE │ │ ├── bson/ │ │ │ └── objectid.go │ │ ├── bson.go │ │ ├── date.go │ │ ├── default.go │ │ ├── doc.go │ │ ├── duration.go │ │ ├── format.go │ │ └── time.go │ ├── kubectl/ │ │ ├── LICENSE │ │ └── pkg/ │ │ └── util/ │ │ ├── interrupt/ │ │ │ └── interrupt.go │ │ └── term/ │ │ ├── resize.go │ │ ├── resizeevents.go │ │ ├── resizeevents_windows.go │ │ ├── term.go │ │ └── term_writer.go │ ├── metrics/ │ │ ├── LICENSE │ │ └── pkg/ │ │ └── apis/ │ │ └── metrics/ │ │ ├── doc.go │ │ ├── register.go │ │ ├── types.go │ │ ├── v1beta1/ │ │ │ ├── doc.go │ │ │ ├── generated.pb.go │ │ │ ├── generated.proto │ │ │ ├── register.go │ │ │ ├── types.go │ │ │ ├── zz_generated.conversion.go │ │ │ └── zz_generated.deepcopy.go │ │ └── zz_generated.deepcopy.go │ └── utils/ │ ├── LICENSE │ ├── buffer/ │ │ └── ring_growing.go │ ├── clock/ │ │ ├── README.md │ │ ├── clock.go │ │ └── testing/ │ │ ├── fake_clock.go │ │ └── simple_interval_clock.go │ ├── internal/ │ │ └── third_party/ │ │ └── forked/ │ │ └── golang/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── golang-lru/ │ │ │ └── lru.go │ │ └── net/ │ │ ├── ip.go │ │ └── parse.go │ ├── lru/ │ │ └── lru.go │ ├── net/ │ │ ├── ipfamily.go │ │ ├── ipnet.go │ │ ├── multi_listen.go │ │ ├── net.go │ │ ├── parse.go │ │ └── port.go │ ├── path/ │ │ └── file.go │ ├── pointer/ │ │ ├── OWNERS │ │ ├── README.md │ │ └── pointer.go │ ├── ptr/ │ │ ├── OWNERS │ │ ├── README.md │ │ └── ptr.go │ └── trace/ │ ├── README.md │ └── trace.go ├── modules.txt ├── mvdan.cc/ │ └── sh/ │ └── v3/ │ ├── LICENSE │ ├── expand/ │ │ ├── arith.go │ │ ├── braces.go │ │ ├── doc.go │ │ ├── environ.go │ │ ├── expand.go │ │ └── param.go │ ├── fileutil/ │ │ └── file.go │ ├── interp/ │ │ ├── api.go │ │ ├── builtin.go │ │ ├── handler.go │ │ ├── os_unix.go │ │ ├── os_windows.go │ │ ├── runner.go │ │ ├── test.go │ │ ├── test_classic.go │ │ ├── trace.go │ │ └── vars.go │ ├── pattern/ │ │ └── pattern.go │ └── syntax/ │ ├── braces.go │ ├── canonical.sh │ ├── doc.go │ ├── lexer.go │ ├── nodes.go │ ├── parser.go │ ├── parser_arithm.go │ ├── printer.go │ ├── quote.go │ ├── quotestate_string.go │ ├── simplify.go │ ├── token_string.go │ ├── tokens.go │ └── walk.go ├── sigs.k8s.io/ │ ├── apiserver-network-proxy/ │ │ └── konnectivity-client/ │ │ ├── LICENSE │ │ ├── pkg/ │ │ │ ├── client/ │ │ │ │ ├── client.go │ │ │ │ ├── conn.go │ │ │ │ └── metrics/ │ │ │ │ └── metrics.go │ │ │ └── common/ │ │ │ └── metrics/ │ │ │ └── metrics.go │ │ └── proto/ │ │ └── client/ │ │ ├── client.pb.go │ │ ├── client.proto │ │ └── client_grpc.pb.go │ ├── controller-runtime/ │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── .gomodcheck.yaml │ │ ├── CONTRIBUTING.md │ │ ├── FAQ.md │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── OWNERS │ │ ├── OWNERS_ALIASES │ │ ├── README.md │ │ ├── RELEASE.md │ │ ├── SECURITY_CONTACTS │ │ ├── TMP-LOGGING.md │ │ ├── VERSIONING.md │ │ ├── alias.go │ │ ├── code-of-conduct.md │ │ ├── doc.go │ │ └── pkg/ │ │ ├── builder/ │ │ │ ├── controller.go │ │ │ ├── doc.go │ │ │ ├── options.go │ │ │ └── webhook.go │ │ ├── cache/ │ │ │ ├── cache.go │ │ │ ├── delegating_by_gvk_cache.go │ │ │ ├── doc.go │ │ │ ├── informer_cache.go │ │ │ ├── internal/ │ │ │ │ ├── cache_reader.go │ │ │ │ ├── informers.go │ │ │ │ └── selector.go │ │ │ └── multi_namespace_cache.go │ │ ├── certwatcher/ │ │ │ ├── certwatcher.go │ │ │ ├── doc.go │ │ │ └── metrics/ │ │ │ └── metrics.go │ │ ├── client/ │ │ │ ├── apiutil/ │ │ │ │ ├── apimachinery.go │ │ │ │ ├── errors.go │ │ │ │ └── restmapper.go │ │ │ ├── client.go │ │ │ ├── client_rest_resources.go │ │ │ ├── codec.go │ │ │ ├── config/ │ │ │ │ ├── config.go │ │ │ │ └── doc.go │ │ │ ├── doc.go │ │ │ ├── dryrun.go │ │ │ ├── fieldowner.go │ │ │ ├── fieldvalidation.go │ │ │ ├── interfaces.go │ │ │ ├── metadata_client.go │ │ │ ├── namespaced_client.go │ │ │ ├── object.go │ │ │ ├── options.go │ │ │ ├── patch.go │ │ │ ├── typed_client.go │ │ │ ├── unstructured_client.go │ │ │ └── watch.go │ │ ├── cluster/ │ │ │ ├── cluster.go │ │ │ └── internal.go │ │ ├── config/ │ │ │ └── controller.go │ │ ├── controller/ │ │ │ ├── controller.go │ │ │ ├── controllerutil/ │ │ │ │ ├── controllerutil.go │ │ │ │ └── doc.go │ │ │ ├── doc.go │ │ │ ├── name.go │ │ │ └── priorityqueue/ │ │ │ ├── metrics.go │ │ │ └── priorityqueue.go │ │ ├── conversion/ │ │ │ └── conversion.go │ │ ├── event/ │ │ │ ├── doc.go │ │ │ └── event.go │ │ ├── handler/ │ │ │ ├── doc.go │ │ │ ├── enqueue.go │ │ │ ├── enqueue_mapped.go │ │ │ ├── enqueue_owner.go │ │ │ └── eventhandler.go │ │ ├── healthz/ │ │ │ ├── doc.go │ │ │ └── healthz.go │ │ ├── internal/ │ │ │ ├── controller/ │ │ │ │ ├── controller.go │ │ │ │ └── metrics/ │ │ │ │ └── metrics.go │ │ │ ├── field/ │ │ │ │ └── selector/ │ │ │ │ └── utils.go │ │ │ ├── httpserver/ │ │ │ │ └── server.go │ │ │ ├── log/ │ │ │ │ └── log.go │ │ │ ├── metrics/ │ │ │ │ └── workqueue.go │ │ │ ├── recorder/ │ │ │ │ └── recorder.go │ │ │ ├── source/ │ │ │ │ ├── event_handler.go │ │ │ │ └── kind.go │ │ │ └── syncs/ │ │ │ └── syncs.go │ │ ├── leaderelection/ │ │ │ ├── doc.go │ │ │ └── leader_election.go │ │ ├── log/ │ │ │ ├── deleg.go │ │ │ ├── log.go │ │ │ ├── null.go │ │ │ └── warning_handler.go │ │ ├── manager/ │ │ │ ├── doc.go │ │ │ ├── internal.go │ │ │ ├── manager.go │ │ │ ├── runnable_group.go │ │ │ ├── server.go │ │ │ └── signals/ │ │ │ ├── doc.go │ │ │ ├── signal.go │ │ │ ├── signal_posix.go │ │ │ └── signal_windows.go │ │ ├── metrics/ │ │ │ ├── client_go_adapter.go │ │ │ ├── doc.go │ │ │ ├── leaderelection.go │ │ │ ├── registry.go │ │ │ ├── server/ │ │ │ │ ├── doc.go │ │ │ │ └── server.go │ │ │ └── workqueue.go │ │ ├── predicate/ │ │ │ ├── doc.go │ │ │ └── predicate.go │ │ ├── reconcile/ │ │ │ ├── doc.go │ │ │ └── reconcile.go │ │ ├── recorder/ │ │ │ └── recorder.go │ │ ├── scheme/ │ │ │ └── scheme.go │ │ ├── source/ │ │ │ ├── doc.go │ │ │ └── source.go │ │ └── webhook/ │ │ ├── admission/ │ │ │ ├── decode.go │ │ │ ├── defaulter_custom.go │ │ │ ├── doc.go │ │ │ ├── http.go │ │ │ ├── metrics/ │ │ │ │ └── metrics.go │ │ │ ├── multi.go │ │ │ ├── response.go │ │ │ ├── validator_custom.go │ │ │ └── webhook.go │ │ ├── alias.go │ │ ├── conversion/ │ │ │ ├── conversion.go │ │ │ └── decoder.go │ │ ├── doc.go │ │ ├── internal/ │ │ │ └── metrics/ │ │ │ └── metrics.go │ │ └── server.go │ ├── json/ │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── Makefile │ │ ├── OWNERS │ │ ├── README.md │ │ ├── SECURITY.md │ │ ├── SECURITY_CONTACTS │ │ ├── code-of-conduct.md │ │ ├── doc.go │ │ ├── internal/ │ │ │ └── golang/ │ │ │ └── encoding/ │ │ │ └── json/ │ │ │ ├── decode.go │ │ │ ├── encode.go │ │ │ ├── fold.go │ │ │ ├── fuzz.go │ │ │ ├── indent.go │ │ │ ├── kubernetes_patch.go │ │ │ ├── scanner.go │ │ │ ├── stream.go │ │ │ ├── tables.go │ │ │ └── tags.go │ │ └── json.go │ ├── structured-merge-diff/ │ │ └── v4/ │ │ ├── LICENSE │ │ ├── fieldpath/ │ │ │ ├── doc.go │ │ │ ├── element.go │ │ │ ├── fromvalue.go │ │ │ ├── managers.go │ │ │ ├── path.go │ │ │ ├── pathelementmap.go │ │ │ ├── serialize-pe.go │ │ │ ├── serialize.go │ │ │ └── set.go │ │ ├── merge/ │ │ │ ├── conflict.go │ │ │ └── update.go │ │ ├── schema/ │ │ │ ├── doc.go │ │ │ ├── elements.go │ │ │ ├── equals.go │ │ │ └── schemaschema.go │ │ ├── typed/ │ │ │ ├── compare.go │ │ │ ├── doc.go │ │ │ ├── helpers.go │ │ │ ├── merge.go │ │ │ ├── parser.go │ │ │ ├── reconcile_schema.go │ │ │ ├── remove.go │ │ │ ├── tofieldset.go │ │ │ ├── typed.go │ │ │ └── validate.go │ │ └── value/ │ │ ├── allocator.go │ │ ├── doc.go │ │ ├── fields.go │ │ ├── jsontagutil.go │ │ ├── list.go │ │ ├── listreflect.go │ │ ├── listunstructured.go │ │ ├── map.go │ │ ├── mapreflect.go │ │ ├── mapunstructured.go │ │ ├── reflectcache.go │ │ ├── scalar.go │ │ ├── structreflect.go │ │ ├── value.go │ │ ├── valuereflect.go │ │ └── valueunstructured.go │ └── yaml/ │ ├── .gitignore │ ├── .travis.yml │ ├── CONTRIBUTING.md │ ├── LICENSE │ ├── OWNERS │ ├── README.md │ ├── RELEASE.md │ ├── SECURITY_CONTACTS │ ├── code-of-conduct.md │ ├── fields.go │ ├── goyaml.v2/ │ │ ├── LICENSE │ │ ├── LICENSE.libyaml │ │ ├── NOTICE │ │ ├── OWNERS │ │ ├── README.md │ │ ├── apic.go │ │ ├── decode.go │ │ ├── emitterc.go │ │ ├── encode.go │ │ ├── parserc.go │ │ ├── readerc.go │ │ ├── resolve.go │ │ ├── scannerc.go │ │ ├── sorter.go │ │ ├── writerc.go │ │ ├── yaml.go │ │ ├── yamlh.go │ │ └── yamlprivateh.go │ ├── yaml.go │ └── yaml_go110.go └── tailscale.com/ ├── .gitattributes ├── .gitignore ├── .golangci.yml ├── ALPINE.txt ├── AUTHORS ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── Dockerfile.base ├── LICENSE ├── Makefile ├── PATENTS ├── README.md ├── SECURITY.md ├── VERSION.txt ├── api.md ├── appc/ │ └── appconnector.go ├── assert_ts_toolchain_match.go ├── atomicfile/ │ └── atomicfile.go ├── build_dist.sh ├── build_docker.sh ├── client/ │ ├── tailscale/ │ │ ├── acl.go │ │ ├── apitype/ │ │ │ ├── apitype.go │ │ │ └── controltype.go │ │ ├── devices.go │ │ ├── dns.go │ │ ├── keys.go │ │ ├── localclient.go │ │ ├── required_version.go │ │ ├── routes.go │ │ ├── tailnet.go │ │ └── tailscale.go │ └── web/ │ ├── assets.go │ ├── auth.go │ ├── index.html │ ├── package.json │ ├── qnap.go │ ├── styles.json │ ├── synology.go │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── vite.config.ts │ └── web.go ├── clientupdate/ │ ├── clientupdate.go │ ├── clientupdate_downloads.go │ ├── clientupdate_not_downloads.go │ ├── clientupdate_notwindows.go │ ├── clientupdate_windows.go │ └── distsign/ │ ├── distsign.go │ ├── roots/ │ │ ├── crawshaw-root.pem │ │ └── distsign-prod-root-1-pub.pem │ └── roots.go ├── control/ │ ├── controlbase/ │ │ ├── conn.go │ │ ├── handshake.go │ │ └── messages.go │ ├── controlclient/ │ │ ├── auto.go │ │ ├── client.go │ │ ├── direct.go │ │ ├── map.go │ │ ├── noise.go │ │ ├── sign.go │ │ ├── sign_supported.go │ │ ├── sign_unsupported.go │ │ └── status.go │ ├── controlhttp/ │ │ ├── client.go │ │ ├── client_common.go │ │ ├── client_websocket.go │ │ ├── constants.go │ │ └── controlhttpcommon/ │ │ └── controlhttpcommon.go │ └── controlknobs/ │ └── controlknobs.go ├── derp/ │ ├── README.md │ ├── derp.go │ ├── derp_client.go │ ├── derp_server.go │ ├── derp_server_default.go │ ├── derp_server_linux.go │ ├── derphttp/ │ │ ├── derphttp_client.go │ │ ├── derphttp_server.go │ │ ├── mesh_client.go │ │ ├── websocket.go │ │ └── websocket_stub.go │ └── dropreason_string.go ├── disco/ │ ├── disco.go │ ├── disco_fuzzer.go │ └── pcap.go ├── doctor/ │ ├── doctor.go │ ├── ethtool/ │ │ ├── ethtool.go │ │ ├── ethtool_linux.go │ │ └── ethtool_other.go │ ├── permissions/ │ │ ├── permissions.go │ │ ├── permissions_bsd.go │ │ ├── permissions_linux.go │ │ └── permissions_other.go │ └── routetable/ │ └── routetable.go ├── drive/ │ ├── drive_clone.go │ ├── drive_view.go │ ├── local.go │ ├── remote.go │ ├── remote_nonunix.go │ ├── remote_permissions.go │ └── remote_unix.go ├── envknob/ │ ├── envknob.go │ ├── envknob_nottest.go │ ├── envknob_testable.go │ └── featureknob/ │ └── featureknob.go ├── flake.nix ├── go.mod.sri ├── go.toolchain.branch ├── go.toolchain.rev ├── header.txt ├── health/ │ ├── args.go │ ├── health.go │ ├── healthmsg/ │ │ └── healthmsg.go │ ├── state.go │ └── warnings.go ├── hostinfo/ │ ├── hostinfo.go │ ├── hostinfo_darwin.go │ ├── hostinfo_freebsd.go │ ├── hostinfo_linux.go │ ├── hostinfo_uname.go │ ├── hostinfo_windows.go │ ├── packagetype_container.go │ └── wol.go ├── internal/ │ └── noiseconn/ │ └── conn.go ├── ipn/ │ ├── backend.go │ ├── conf.go │ ├── conffile/ │ │ ├── cloudconf.go │ │ ├── conffile.go │ │ └── conffile_hujson.go │ ├── doc.go │ ├── ipn_clone.go │ ├── ipn_view.go │ ├── ipnauth/ │ │ ├── actor.go │ │ ├── ipnauth.go │ │ ├── ipnauth_notwindows.go │ │ ├── ipnauth_windows.go │ │ └── test_actor.go │ ├── ipnlocal/ │ │ ├── autoupdate.go │ │ ├── autoupdate_disabled.go │ │ ├── breaktcp_darwin.go │ │ ├── breaktcp_linux.go │ │ ├── bus.go │ │ ├── c2n.go │ │ ├── c2n_pprof.go │ │ ├── cert.go │ │ ├── cert_js.go │ │ ├── drive.go │ │ ├── expiry.go │ │ ├── local.go │ │ ├── network-lock.go │ │ ├── peerapi.go │ │ ├── peerapi_h2c.go │ │ ├── peerapi_macios_ext.go │ │ ├── profiles.go │ │ ├── profiles_notwindows.go │ │ ├── profiles_windows.go │ │ ├── serve.go │ │ ├── ssh.go │ │ ├── ssh_stub.go │ │ ├── taildrop.go │ │ ├── web_client.go │ │ └── web_client_stub.go │ ├── ipnstate/ │ │ ├── ipnstate.go │ │ └── ipnstate_clone.go │ ├── localapi/ │ │ ├── cert.go │ │ ├── debugderp.go │ │ ├── disabled_stubs.go │ │ ├── localapi.go │ │ └── pprof.go │ ├── policy/ │ │ └── policy.go │ ├── prefs.go │ ├── serve.go │ ├── store/ │ │ ├── awsstore/ │ │ │ ├── store_aws.go │ │ │ └── store_aws_stub.go │ │ ├── kubestore/ │ │ │ └── store_kube.go │ │ ├── mem/ │ │ │ └── store_mem.go │ │ ├── store_aws.go │ │ ├── store_kube.go │ │ └── stores.go │ └── store.go ├── kube/ │ ├── kubeapi/ │ │ └── api.go │ ├── kubeclient/ │ │ ├── client.go │ │ └── fake_client.go │ └── kubetypes/ │ ├── grants.go │ └── types.go ├── licenses/ │ ├── android.md │ ├── apple.md │ ├── licenses.go │ ├── tailscale.md │ └── windows.md ├── log/ │ ├── filelogger/ │ │ └── log.go │ └── sockstatlog/ │ └── logger.go ├── logpolicy/ │ └── logpolicy.go ├── logtail/ │ ├── .gitignore │ ├── README.md │ ├── api.md │ ├── backoff/ │ │ └── backoff.go │ ├── buffer.go │ ├── filch/ │ │ ├── filch.go │ │ ├── filch_stub.go │ │ ├── filch_unix.go │ │ └── filch_windows.go │ └── logtail.go ├── metrics/ │ ├── fds_linux.go │ ├── fds_notlinux.go │ ├── metrics.go │ └── multilabelmap.go ├── net/ │ ├── captivedetection/ │ │ ├── captivedetection.go │ │ ├── endpoints.go │ │ ├── rawconn.go │ │ └── rawconn_apple.go │ ├── connstats/ │ │ └── stats.go │ ├── dns/ │ │ ├── config.go │ │ ├── debian_resolvconf.go │ │ ├── direct.go │ │ ├── direct_linux.go │ │ ├── direct_notlinux.go │ │ ├── flush_default.go │ │ ├── flush_windows.go │ │ ├── ini.go │ │ ├── manager.go │ │ ├── manager_darwin.go │ │ ├── manager_default.go │ │ ├── manager_freebsd.go │ │ ├── manager_linux.go │ │ ├── manager_openbsd.go │ │ ├── manager_windows.go │ │ ├── nm.go │ │ ├── noop.go │ │ ├── nrpt_windows.go │ │ ├── openresolv.go │ │ ├── osconfig.go │ │ ├── publicdns/ │ │ │ └── publicdns.go │ │ ├── quad100.go │ │ ├── recursive/ │ │ │ └── recursive.go │ │ ├── resolvconf-workaround.sh │ │ ├── resolvconf.go │ │ ├── resolvconffile/ │ │ │ └── resolvconffile.go │ │ ├── resolvconfpath_default.go │ │ ├── resolvconfpath_gokrazy.go │ │ ├── resolvd.go │ │ ├── resolved.go │ │ ├── resolver/ │ │ │ ├── debug.go │ │ │ ├── forwarder.go │ │ │ ├── macios_ext.go │ │ │ └── tsdns.go │ │ ├── utf.go │ │ └── wsl_windows.go │ ├── dnscache/ │ │ ├── dnscache.go │ │ └── messagecache.go │ ├── dnsfallback/ │ │ ├── dns-fallback-servers.json │ │ └── dnsfallback.go │ ├── flowtrack/ │ │ └── flowtrack.go │ ├── ipset/ │ │ └── ipset.go │ ├── memnet/ │ │ ├── conn.go │ │ ├── listener.go │ │ ├── memnet.go │ │ └── pipe.go │ ├── netaddr/ │ │ └── netaddr.go │ ├── netcheck/ │ │ ├── netcheck.go │ │ └── standalone.go │ ├── neterror/ │ │ ├── neterror.go │ │ ├── neterror_linux.go │ │ └── neterror_windows.go │ ├── netkernelconf/ │ │ ├── netkernelconf.go │ │ ├── netkernelconf_default.go │ │ └── netkernelconf_linux.go │ ├── netknob/ │ │ └── netknob.go │ ├── netmon/ │ │ ├── defaultroute_bsd.go │ │ ├── defaultroute_darwin.go │ │ ├── interfaces_android.go │ │ ├── interfaces_bsd.go │ │ ├── interfaces_darwin.go │ │ ├── interfaces_defaultrouteif_todo.go │ │ ├── interfaces_freebsd.go │ │ ├── interfaces_linux.go │ │ ├── interfaces_windows.go │ │ ├── netmon.go │ │ ├── netmon_darwin.go │ │ ├── netmon_freebsd.go │ │ ├── netmon_linux.go │ │ ├── netmon_polling.go │ │ ├── netmon_windows.go │ │ ├── polling.go │ │ └── state.go │ ├── netns/ │ │ ├── mksyscall.go │ │ ├── netns.go │ │ ├── netns_android.go │ │ ├── netns_darwin.go │ │ ├── netns_default.go │ │ ├── netns_dw.go │ │ ├── netns_linux.go │ │ ├── netns_windows.go │ │ ├── socks.go │ │ └── zsyscall_windows.go │ ├── netstat/ │ │ ├── netstat.go │ │ ├── netstat_noimpl.go │ │ └── netstat_windows.go │ ├── netutil/ │ │ ├── default_interface_portable.go │ │ ├── ip_forward.go │ │ ├── netutil.go │ │ └── routes.go │ ├── packet/ │ │ ├── checksum/ │ │ │ └── checksum.go │ │ ├── doc.go │ │ ├── header.go │ │ ├── icmp.go │ │ ├── icmp4.go │ │ ├── icmp6.go │ │ ├── ip4.go │ │ ├── ip6.go │ │ ├── packet.go │ │ ├── tsmp.go │ │ ├── udp4.go │ │ └── udp6.go │ ├── ping/ │ │ └── ping.go │ ├── portmapper/ │ │ ├── disabled_stubs.go │ │ ├── legacy_upnp.go │ │ ├── pcp.go │ │ ├── pcpresultcode_string.go │ │ ├── pmpresultcode_string.go │ │ ├── portmapper.go │ │ └── upnp.go │ ├── proxymux/ │ │ └── mux.go │ ├── routetable/ │ │ ├── routetable.go │ │ ├── routetable_bsd.go │ │ ├── routetable_darwin.go │ │ ├── routetable_freebsd.go │ │ ├── routetable_linux.go │ │ └── routetable_other.go │ ├── socks5/ │ │ └── socks5.go │ ├── sockstats/ │ │ ├── label_string.go │ │ ├── sockstats.go │ │ ├── sockstats_noop.go │ │ ├── sockstats_tsgo.go │ │ └── sockstats_tsgo_darwin.go │ ├── stun/ │ │ ├── stun.go │ │ └── stun_fuzzer.go │ ├── tcpinfo/ │ │ ├── tcpinfo.go │ │ ├── tcpinfo_darwin.go │ │ ├── tcpinfo_linux.go │ │ └── tcpinfo_other.go │ ├── tlsdial/ │ │ ├── blockblame/ │ │ │ └── blockblame.go │ │ └── tlsdial.go │ ├── tsaddr/ │ │ └── tsaddr.go │ ├── tsdial/ │ │ ├── dnsmap.go │ │ ├── dohclient.go │ │ ├── peerapi_macios_ext.go │ │ └── tsdial.go │ ├── tshttpproxy/ │ │ ├── mksyscall.go │ │ ├── tshttpproxy.go │ │ ├── tshttpproxy_linux.go │ │ ├── tshttpproxy_synology.go │ │ ├── tshttpproxy_windows.go │ │ └── zsyscall_windows.go │ ├── tstun/ │ │ ├── fake.go │ │ ├── ifstatus_noop.go │ │ ├── ifstatus_windows.go │ │ ├── linkattrs_linux.go │ │ ├── linkattrs_notlinux.go │ │ ├── mtu.go │ │ ├── tap_linux.go │ │ ├── tstun_stub.go │ │ ├── tun.go │ │ ├── tun_linux.go │ │ ├── tun_macos.go │ │ ├── tun_notwindows.go │ │ ├── tun_windows.go │ │ ├── wrap.go │ │ ├── wrap_linux.go │ │ └── wrap_noop.go │ └── wsconn/ │ └── wsconn.go ├── omit/ │ ├── aws_def.go │ ├── aws_omit.go │ └── omit.go ├── paths/ │ ├── migrate.go │ ├── paths.go │ ├── paths_unix.go │ └── paths_windows.go ├── portlist/ │ ├── clean.go │ ├── netstat.go │ ├── poller.go │ ├── portlist.go │ ├── portlist_linux.go │ ├── portlist_macos.go │ └── portlist_windows.go ├── posture/ │ ├── doc.go │ ├── hwaddr.go │ ├── serialnumber_ios.go │ ├── serialnumber_macos.go │ ├── serialnumber_notmacos.go │ └── serialnumber_stub.go ├── proxymap/ │ └── proxymap.go ├── pull-toolchain.sh ├── safesocket/ │ ├── pipe_windows.go │ ├── safesocket.go │ ├── safesocket_darwin.go │ ├── safesocket_js.go │ ├── safesocket_plan9.go │ ├── safesocket_ps.go │ ├── unixsocket.go │ └── zsyscall_windows.go ├── shell.nix ├── staticcheck.conf ├── syncs/ │ ├── locked.go │ ├── pool.go │ ├── shardedmap.go │ └── syncs.go ├── tailcfg/ │ ├── c2ntypes.go │ ├── derpmap.go │ ├── proto_port_range.go │ ├── tailcfg.go │ ├── tailcfg_clone.go │ ├── tailcfg_view.go │ └── tka.go ├── taildrop/ │ ├── delete.go │ ├── resume.go │ ├── retrieve.go │ ├── send.go │ └── taildrop.go ├── tempfork/ │ └── heap/ │ └── heap.go ├── tka/ │ ├── aum.go │ ├── builder.go │ ├── deeplink.go │ ├── key.go │ ├── sig.go │ ├── state.go │ ├── sync.go │ ├── tailchonk.go │ ├── tka.go │ └── tka_clone.go ├── tsconst/ │ └── interface.go ├── tsd/ │ └── tsd.go ├── tsnet/ │ └── tsnet.go ├── tstime/ │ ├── jitter.go │ ├── mono/ │ │ └── mono.go │ ├── rate/ │ │ ├── rate.go │ │ └── value.go │ └── tstime.go ├── tsweb/ │ └── varz/ │ └── varz.go ├── types/ │ ├── appctype/ │ │ └── appconnector.go │ ├── dnstype/ │ │ ├── dnstype.go │ │ ├── dnstype_clone.go │ │ ├── dnstype_view.go │ │ └── messagetypes-string.go │ ├── empty/ │ │ └── message.go │ ├── ipproto/ │ │ └── ipproto.go │ ├── key/ │ │ ├── chal.go │ │ ├── control.go │ │ ├── disco.go │ │ ├── doc.go │ │ ├── machine.go │ │ ├── nl.go │ │ ├── node.go │ │ └── util.go │ ├── lazy/ │ │ ├── deferred.go │ │ ├── lazy.go │ │ └── unsync.go │ ├── logger/ │ │ ├── logger.go │ │ ├── rusage.go │ │ ├── rusage_stub.go │ │ ├── rusage_syscall.go │ │ └── tokenbucket.go │ ├── logid/ │ │ └── id.go │ ├── netlogtype/ │ │ └── netlogtype.go │ ├── netmap/ │ │ ├── netmap.go │ │ └── nodemut.go │ ├── nettype/ │ │ └── nettype.go │ ├── opt/ │ │ ├── bool.go │ │ └── value.go │ ├── persist/ │ │ ├── persist.go │ │ ├── persist_clone.go │ │ └── persist_view.go │ ├── preftype/ │ │ └── netfiltermode.go │ ├── ptr/ │ │ └── ptr.go │ ├── result/ │ │ └── result.go │ ├── structs/ │ │ └── structs.go │ ├── tkatype/ │ │ └── tkatype.go │ └── views/ │ └── views.go ├── update-flake.sh ├── util/ │ ├── cibuild/ │ │ └── cibuild.go │ ├── clientmetric/ │ │ └── clientmetric.go │ ├── cloudenv/ │ │ └── cloudenv.go │ ├── cmpver/ │ │ └── version.go │ ├── ctxkey/ │ │ └── key.go │ ├── deephash/ │ │ ├── debug.go │ │ ├── deephash.go │ │ ├── pointer.go │ │ ├── pointer_norace.go │ │ ├── pointer_race.go │ │ └── types.go │ ├── dirwalk/ │ │ ├── dirwalk.go │ │ └── dirwalk_linux.go │ ├── dnsname/ │ │ └── dnsname.go │ ├── execqueue/ │ │ └── execqueue.go │ ├── goroutines/ │ │ └── goroutines.go │ ├── groupmember/ │ │ └── groupmember.go │ ├── hashx/ │ │ └── block512.go │ ├── httphdr/ │ │ └── httphdr.go │ ├── httpm/ │ │ └── httpm.go │ ├── lineiter/ │ │ └── lineiter.go │ ├── linuxfw/ │ │ ├── detector.go │ │ ├── fake.go │ │ ├── helpers.go │ │ ├── iptables.go │ │ ├── iptables_for_svcs.go │ │ ├── iptables_runner.go │ │ ├── linuxfw.go │ │ ├── linuxfw_unsupported.go │ │ ├── nftables.go │ │ ├── nftables_for_svcs.go │ │ ├── nftables_runner.go │ │ └── nftables_types.go │ ├── mak/ │ │ └── mak.go │ ├── multierr/ │ │ └── multierr.go │ ├── must/ │ │ └── must.go │ ├── nocasemaps/ │ │ └── nocase.go │ ├── osdiag/ │ │ ├── internal/ │ │ │ └── wsc/ │ │ │ └── wsc_windows.go │ │ ├── mksyscall.go │ │ ├── osdiag.go │ │ ├── osdiag_notwindows.go │ │ ├── osdiag_windows.go │ │ └── zsyscall_windows.go │ ├── osshare/ │ │ ├── filesharingstatus_noop.go │ │ └── filesharingstatus_windows.go │ ├── osuser/ │ │ ├── group_ids.go │ │ └── user.go │ ├── progresstracking/ │ │ └── progresstracking.go │ ├── race/ │ │ └── race.go │ ├── racebuild/ │ │ ├── off.go │ │ ├── on.go │ │ └── racebuild.go │ ├── rands/ │ │ ├── cheap.go │ │ └── rands.go │ ├── ringbuffer/ │ │ └── ringbuffer.go │ ├── set/ │ │ ├── handle.go │ │ ├── set.go │ │ └── slice.go │ ├── singleflight/ │ │ └── singleflight.go │ ├── slicesx/ │ │ └── slicesx.go │ ├── syspolicy/ │ │ ├── handler.go │ │ ├── internal/ │ │ │ ├── internal.go │ │ │ ├── loggerx/ │ │ │ │ └── logger.go │ │ │ └── metrics/ │ │ │ ├── metrics.go │ │ │ └── test_handler.go │ │ ├── policy_keys.go │ │ ├── rsop/ │ │ │ ├── change_callbacks.go │ │ │ ├── resultant_policy.go │ │ │ ├── rsop.go │ │ │ └── store_registration.go │ │ ├── setting/ │ │ │ ├── errors.go │ │ │ ├── key.go │ │ │ ├── origin.go │ │ │ ├── policy_scope.go │ │ │ ├── raw_item.go │ │ │ ├── setting.go │ │ │ ├── snapshot.go │ │ │ ├── summary.go │ │ │ └── types.go │ │ ├── source/ │ │ │ ├── env_policy_store.go │ │ │ ├── policy_reader.go │ │ │ ├── policy_source.go │ │ │ ├── policy_store_windows.go │ │ │ └── test_store.go │ │ ├── syspolicy.go │ │ └── syspolicy_windows.go │ ├── sysresources/ │ │ ├── memory.go │ │ ├── memory_bsd.go │ │ ├── memory_darwin.go │ │ ├── memory_linux.go │ │ ├── memory_unsupported.go │ │ └── sysresources.go │ ├── systemd/ │ │ ├── doc.go │ │ ├── systemd_linux.go │ │ └── systemd_nonlinux.go │ ├── testenv/ │ │ └── testenv.go │ ├── truncate/ │ │ └── truncate.go │ ├── uniq/ │ │ └── slice.go │ ├── usermetric/ │ │ ├── metrics.go │ │ └── usermetric.go │ ├── vizerror/ │ │ └── vizerror.go │ ├── winutil/ │ │ ├── authenticode/ │ │ │ ├── authenticode_windows.go │ │ │ ├── mksyscall.go │ │ │ └── zsyscall_windows.go │ │ ├── gp/ │ │ │ ├── gp_windows.go │ │ │ ├── mksyscall.go │ │ │ ├── policylock_windows.go │ │ │ ├── watcher_windows.go │ │ │ └── zsyscall_windows.go │ │ ├── mksyscall.go │ │ ├── policy/ │ │ │ └── policy_windows.go │ │ ├── restartmgr_windows.go │ │ ├── startupinfo_windows.go │ │ ├── svcdiag_windows.go │ │ ├── userprofile_windows.go │ │ ├── winenv/ │ │ │ ├── mksyscall.go │ │ │ ├── winenv_windows.go │ │ │ └── zsyscall_windows.go │ │ ├── winutil.go │ │ ├── winutil_notwindows.go │ │ ├── winutil_windows.go │ │ └── zsyscall_windows.go │ └── zstdframe/ │ ├── options.go │ └── zstd.go ├── version-embed.go └── wgengine/ ├── capture/ │ ├── capture.go │ └── ts-dissector.lua ├── filter/ │ ├── filter.go │ ├── filtertype/ │ │ ├── filtertype.go │ │ └── filtertype_clone.go │ ├── match.go │ └── tailcfg.go ├── magicsock/ │ ├── batching_conn.go │ ├── batching_conn_default.go │ ├── batching_conn_linux.go │ ├── blockforever_conn.go │ ├── cloudinfo.go │ ├── cloudinfo_nocloud.go │ ├── debughttp.go │ ├── debugknobs.go │ ├── debugknobs_stubs.go │ ├── derp.go │ ├── discopingpurpose_string.go │ ├── endpoint.go │ ├── endpoint_default.go │ ├── endpoint_stub.go │ ├── endpoint_tracker.go │ ├── magicsock.go │ ├── magicsock_default.go │ ├── magicsock_linux.go │ ├── magicsock_notwindows.go │ ├── magicsock_windows.go │ ├── peermap.go │ ├── peermtu.go │ ├── peermtu_darwin.go │ ├── peermtu_linux.go │ ├── peermtu_stubs.go │ ├── peermtu_unix.go │ └── rebinding_conn.go ├── mem_ios.go ├── netlog/ │ └── logger.go ├── netstack/ │ ├── gro/ │ │ ├── gro.go │ │ ├── gro_default.go │ │ └── gro_ios.go │ ├── link_endpoint.go │ ├── netstack.go │ ├── netstack_linux.go │ ├── netstack_tcpbuf_default.go │ ├── netstack_tcpbuf_ios.go │ ├── netstack_userping.go │ └── netstack_userping_apple.go ├── pendopen.go ├── router/ │ ├── callback.go │ ├── consolidating_router.go │ ├── ifconfig_windows.go │ ├── router.go │ ├── router_darwin.go │ ├── router_default.go │ ├── router_fake.go │ ├── router_freebsd.go │ ├── router_linux.go │ ├── router_openbsd.go │ ├── router_userspace_bsd.go │ ├── router_windows.go │ └── runner.go ├── userspace.go ├── watchdog.go ├── watchdog_js.go ├── wgcfg/ │ ├── config.go │ ├── device.go │ ├── nmcfg/ │ │ └── nmcfg.go │ ├── parser.go │ ├── wgcfg_clone.go │ └── writer.go ├── wgengine.go ├── wgint/ │ └── wgint.go ├── wglog/ │ └── wglog.go └── winnet/ ├── winnet.go └── winnet_windows.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .devcontainer/devcontainer.json ================================================ { "name": "DevPod Development", "image": "ghcr.io/loft-sh/devpod:dev-6b64450abdb0ebc2ce7f663f9ff935c56679f8b6", "remoteUser": "devpod", "postCreateCommand": "bash ./.devcontainer/post_create.sh", // Required for DinD "privileged": true } ================================================ FILE: .devcontainer/post_create.sh ================================================ #!/usr/bin/env bash set -euo pipefail log() { echo "[POST_CREATE] $*" } # Start docker daemon. The script should've been put here by the DinD devcontainer feature log "Starting Docker Daemon" sudo /usr/local/share/docker-init.sh # # Add our user to docker group sudo usermod -aG docker $USER log "Installing docker provider with default options" devpod provider add docker log "Done" ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.md ================================================ --- name: Bug report about: Create a report to help us reproduce and fix a bug labels: - kind/bug --- **What happened?** **What did you expect to happen instead?** **How can we reproduce the bug?** (as minimally and precisely as possible) My *`devcontainer.json`*: ``` { "name": "...", ... } ``` **Local Environment:** - DevPod Version: [use `devpod version`] - Operating System: windows | linux | mac - ARCH of the OS: AMD64 | ARM64 | i386 **DevPod Provider:** - Cloud Provider: google | aws | azure | digitalOcean - Kubernetes Provider: [use `kubectl version`] - Local/remote provider: docker | ssh - Custom provider: provide imported `provider.yaml` config file **Anything else we need to know?** ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.md ================================================ --- name: Feature request about: Suggest an idea for this project labels: - kind/feature --- **Is your feature request related to a problem?** **Which solution do you suggest?** **Which alternative solutions exist?** **Additional context** ================================================ FILE: .github/devcontainer/Dockerfile ================================================ FROM mcr.microsoft.com/devcontainers/go:1.23-bullseye ARG TARGETOS ARG TARGETARCH # We want to setup our own user later, this removes the built-in VSCode user # that comes with the base image RUN sudo userdel -r vscode -f || true && rm -rf /home/vscode # Install Node.js RUN apt-get update \ && apt-get install -y --no-install-recommends curl ca-certificates gnupg \ && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get update \ && apt-get install -y --no-install-recommends nodejs \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # Install Protobuf compiler RUN apt-get update \ && apt-get install -y --no-install-recommends protobuf-compiler \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # Install kind RUN curl -L -o kind "https://kind.sigs.k8s.io/dl/v0.17.0/kind-linux-${TARGETARCH}" && install -c -m 0755 kind /usr/local/bin && rm kind # Install alacritty terminfo RUN wget https://raw.githubusercontent.com/alacritty/alacritty/master/extra/alacritty.info \ && sudo tic -xe alacritty,alacritty-direct alacritty.info \ && rm alacritty.info ================================================ FILE: .github/devcontainer/devcontainer.json ================================================ { "name": "DevPod Development Image", "build": { "dockerfile": "Dockerfile", "context": "." }, "features": { "ghcr.io/devcontainers/features/common-utils:2": { "installZsh": true, "configureZshAsDefaultShell": true, "installOhMyZsh": true, "installOhMyZshConfig": true, "upgradePackages": true, "username": "devpod" }, "ghcr.io/devcontainers/features/github-cli:1": {}, "ghcr.io/devcontainers/features/docker-in-docker:2": { "version": "latest" }, "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": { "version": "latest" }, "ghcr.io/dhoeric/features/k9s:1": { "version": "latest" } } } ================================================ FILE: .github/licenses.tmpl ================================================ --- title: Open Source Licenses sidebar_label: OSS Licenses --- # Devpod dependencies The following open source dependencies are used to build the [DevPod][] CLI. [DevPod]: https://devpod.sh ## Go Packages Some packages may only be included on certain architectures or operating systems. {{ range . }} - [{{.Name}}](https://pkg.go.dev/{{.Name}}) ([{{.LicenseName}}]({{.LicenseURL}})) {{- end }} ================================================ FILE: .github/workflows/build-devcontainer-image.yaml ================================================ name: Build Dev Container on: workflow_dispatch: push: branches: - "main" paths: - ".github/devcontainer/**" jobs: build-and-push: runs-on: ubuntu-latest permissions: packages: "write" contents: "write" pull-requests: "write" steps: - name: Checkout id: checkout uses: actions/checkout@v1 - name: Log in to GitHub Container Registry uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v1 - name: Prepare DevPod run: | curl -L -o devpod "https://github.com/loft-sh/devpod/releases/latest/download/devpod-linux-amd64" \ && sudo install -c -m 0755 devpod /usr/local/bin \ && rm -f devpod devpod provider add docker - name: "Build and push image" run: | for ARCH in amd64 arm64; do # Build for $ARCH devpod build . --devcontainer-path .github/devcontainer/devcontainer.json --platform linux/$ARCH --skip-push ID=$(docker images --format "{{.ID}} {{.CreatedAt}} {{.Tag}}" | sort -rk 2 | grep "devpod" | awk 'NR==1{print $1}') echo "found image: $ID" if [ -z "${ID}" ]; then echo "Image ID empty, exiting" exit 0 fi docker image ls docker tag $ID ghcr.io/loft-sh/devpod:dev-$ARCH docker push ghcr.io/loft-sh/devpod:dev-$ARCH done SUFFIX="${{github.sha}}" IMAGE_NAME="devpod:dev-$SUFFIX" echo "IMAGE_NAME=$IMAGE_NAME" >> $GITHUB_ENV # Combine into multi-arch image docker manifest create ghcr.io/loft-sh/$IMAGE_NAME \ --amend ghcr.io/loft-sh/devpod:dev-amd64 \ --amend ghcr.io/loft-sh/devpod:dev-arm64 docker manifest push ghcr.io/loft-sh/$IMAGE_NAME - name: Update devcontainer.json if: ${{ success() }} run: | sed -i "s|\(\"image\": \"\).*|\1ghcr.io/loft-sh/${IMAGE_NAME}\",|" .devcontainer/devcontainer.json - name: Create Pull Request if: ${{ success() }} uses: peter-evans/create-pull-request@v4 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: "Update devcontainer image to ghcr.io/loft-sh/${{ env.IMAGE_NAME }}" title: "Update devcontainer image" body: "This PR updates the devcontainer image to ghcr.io/loft-sh/${{ env.IMAGE_NAME }}" branch: "update-devcontainer-${{ github.sha }}" base: "main" ================================================ FILE: .github/workflows/e2e-tests.yaml ================================================ name: E2E tests on: release: types: [prereleased] workflow_dispatch: {} pull_request: branches: - main paths: - "**.go" - "pkg/**.sh" - "providers/**" - "!**_test.go" # exclude test files to ignore unit test changes - "e2e/**_test.go" # include test files in e2e again - ".github/workflows/e2e-tests.yaml" concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: GO111MODULE: on GOFLAGS: -mod=vendor jobs: test-e2e: runs-on: ubuntu-latest strategy: fail-fast: true max-parallel: 16 matrix: label: - "build" - "ide" - "integration" - "machine" - "machineprovider" - "provider" - "ssh" - "up" - "up-docker" - "up-podman" - "up-docker-compose" - "up-docker-build" - "up-docker-compose-build" - "context" steps: - name: Checkout repo uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.21.8 - name: Set up kind k8s cluster uses: engineerd/setup-kind@v0.5.0 with: version: "v0.20.0" image: kindest/node:v1.27.3 - name: Testing kind cluster set-up run: | set -x kubectl cluster-info kubectl get pods -n kube-system -v 10 echo "kubectl config current-context:" $(kubectl config current-context) echo "KUBECONFIG env var:" ${KUBECONFIG} - name: Build binary and copy to the E2E directory working-directory: ./e2e run: | chmod +x ../hack/build-e2e.sh BUILDDIR=bin SRCDIR=".." ../hack/build-e2e.sh - name: E2E test working-directory: ./e2e run: | sudo KUBECONFIG=/home/runner/.kube/config \ GH_USERNAME=${GH_USERNAME} \ GH_ACCESS_TOKEN=${GH_ACCESS_TOKEN} \ go test -v -ginkgo.v -timeout 3600s --ginkgo.label-filter=${{ matrix.label }} env: GH_USERNAME: ${{ secrets.GH_PRIVATE_REPO_USER_TEST }} GH_ACCESS_TOKEN: ${{ secrets.GH_PRIVATE_REPO_TOKEN_TEST }} test-e2e-windows: runs-on: self-hosted-windows # We run this only on PRs, for pre-releases we run the full separate workflow if: ${{ github.event_name == 'pull_request' }} strategy: fail-fast: true max-parallel: 1 matrix: label: - "build" - "ide" - "ssh" - "up-docker" - "up-docker-build" - "up-docker-compose" # - "up-docker-wsl" steps: - name: Git set line ending run: | git config --global core.autocrlf false - name: Checkout repo uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.21.8 - name: Build binary and copy to the E2E directory run: | mkdir e2e\bin go build -ldflags "-s -w" -o e2e\bin\devpod-windows-amd64.exe $Env:GOOS = "linux"; $Env:GOARCH = "amd64"; go build -ldflags "-s -w" -o e2e\bin\devpod-linux-amd64 - name: E2E test working-directory: .\e2e run: | go run github.com/onsi/ginkgo/v2/ginkgo -r --timeout=3600s --label-filter=${{ matrix.label }} - name: Container cleanup if: ${{ always() }} run: | if (Test-Path C:\Users\loft-user\.devpod\) { Remove-Item -Recurse C:\Users\loft-user\.devpod\ } sh -c "docker ps -q -a | xargs docker rm -f || :" sh -c "docker images --format '{{.Repository}}:{{.Tag}},{{.ID}}' | grep -E 'devpod|none|temp|^test' | cut -d',' -f2 | xargs docker rmi -f || :" sh -c "docker images --format '{{.Tag}}|{{.Digest}}' | grep none | cut -d'|' -f1 | xargs docker rmi -f || :" ================================================ FILE: .github/workflows/e2e-win-full-tests.yaml ================================================ name: E2E Win full tests on: release: types: [prereleased] workflow_dispatch: {} concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: GO111MODULE: on GOFLAGS: -mod=vendor jobs: test-e2e-windows: runs-on: self-hosted-windows strategy: fail-fast: true max-parallel: 1 matrix: label: - "build" - "ide" - "ssh" - "up-docker" - "up-docker-build" - "up-docker-compose" # - "up-docker-wsl" steps: - name: Git set line ending run: | git config --global core.autocrlf false - name: Checkout repo uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.21.8 - name: Build binary and copy to the E2E directory run: | mkdir e2e\bin go build -ldflags "-s -w" -o e2e\bin\devpod-windows-amd64.exe $Env:GOOS = "linux"; $Env:GOARCH = "amd64"; go build -ldflags "-s -w" -o e2e\bin\devpod-linux-amd64 - name: E2E test working-directory: .\e2e run: | go run github.com/onsi/ginkgo/v2/ginkgo -r --timeout=3600s --label-filter=${{ matrix.label }} - name: Container cleanup if: ${{ always() }} run: | if (Test-Path C:\Users\loft-user\.devpod\) { Remove-Item -Recurse C:\Users\loft-user\.devpod\ } sh -c "docker ps -q -a | xargs docker rm -f || :" sh -c "docker images --format '{{.Repository}}:{{.Tag}},{{.ID}}' | grep -E 'devpod|none|temp|^test' | cut -d',' -f2 | xargs docker rmi -f || :" sh -c "docker images --format '{{.ID}}|{{.Digest}}' | grep none | cut -d'|' -f1 | xargs docker rmi -f || :" ================================================ FILE: .github/workflows/go-licenses-check.yaml ================================================ name: go-licenses-check on: pull_request: branches: - main paths: - .github/workflows/go-licenses-check.yaml - go.mod workflow_dispatch: {} concurrency: group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: check-licenses: runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v4 with: go-version-file: go.mod - name: Install go-licenses run: | go install github.com/google/go-licenses@v1.6.0 - name: Run go-licenses check run: go-licenses check ./... --ignore github.com/loft-sh env: GOPRIVATE: "github.com/loft-sh/*" ================================================ FILE: .github/workflows/go-licenses.yaml ================================================ name: go-licenses on: push: branches: - main paths: - .github/licenses.tmpl - .github/workflows/go-licenses.yaml - go.mod workflow_dispatch: concurrency: group: ${{ github.workflow }}-$${{ github.head_ref || github.run_id }} cancel-in-progress: true jobs: update-licenses: runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v4 with: go-version-file: go.mod - name: Install go-licenses run: | go install github.com/google/go-licenses@v1.6.0 - name: Run go-licenses run: go-licenses report ./... > docs/pages/licenses/devpod.mdx --template .github/licenses.tmpl --ignore github.com/loft-sh - name: Create pull request uses: peter-evans/create-pull-request@v5 with: token: ${{ secrets.GH_ACCESS_TOKEN }} committer: Loft Bot branch: licenses/devpod commit-message: "license(DevPod): Updated OSS licenses" title: "license(DevPod): Updated OSS licenses" body: Triggered by ${{ github.repository }}@${{ github.sha }} signoff: true delete-branch: true ================================================ FILE: .github/workflows/golangci-lint.yaml ================================================ name: golangci-lint on: push: tags: - v* branches: - master - main pull_request: permissions: contents: read # Optional: allow read access to pull request. Use with `only-new-issues` option. pull-requests: read env: GO111MODULE: on GOFLAGS: -mod=vendor jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/setup-go@v4 with: go-version: "1.21.8" - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: version: v1.60.1 args: --timeout 30m ================================================ FILE: .github/workflows/release.yaml ================================================ name: Publish release on: release: types: [prereleased] jobs: create-release: if: startsWith(github.ref, 'refs/tags/v') == true permissions: contents: write runs-on: ubuntu-22.04 outputs: package_version: ${{ steps.get-version.outputs.package_version }} original_package_version: ${{ steps.get-version.outputs.original_package_version }} release_id: ${{ steps.get-release.outputs.id }} steps: - name: Checkout repository uses: actions/checkout@v3 - name: Setup Node uses: actions/setup-node@v3 with: node-version: 16 - run: npm install semver - name: Get Version uses: actions/github-script@v6 id: get-version with: script: | const semver = require("semver") const refName = `${process.env.GITHUB_REF_NAME}` let version = refName.split("v")[1] core.info(`Original Version: ${version}`) core.setOutput("original_package_version", version) const parsed = semver.parse(version); const supportedPreleases = [ { tag: "alpha", number: 1 }, { tag: "beta", number: 2 }, { tag: "rc", number: 3 }, ]; const maybePrelease = semver.prerelease(version); const maybeSupported = supportedPreleases.find( (p) => p.tag === maybePrelease?.[0] ); // If we have a prelease and it is in the supported range, then we can use it if (maybePrelease && maybeSupported) { version = `${parsed.major}.${parsed.minor}.${parsed.patch}-${ maybeSupported.number }${maybePrelease[1] ?? 0}`; } if(maybePrelease && !maybeSupported) { core.setFailed(`Unsupported prerelease: ${version}`) } core.info(`Version: ${version}`) core.setOutput("package_version", version) - name: Get Release uses: actions/github-script@v6 id: get-release with: script: | // Find the prerelease release in our repo that triggered this workflow const refName = `${process.env.GITHUB_REF_NAME}` const res = await github.rest.repos.listReleases({ owner: context.repo.owner, repo: context.repo.repo, per_page: 10, }) const release = res.data.find((r) => r.tag_name === refName && r.prerelease) if(!release) { core.setFailed("Unable to find prerelease for this workflow") } core.setOutput("id", release.id) build-app: needs: create-release if: startsWith(github.ref, 'refs/tags/v') == true permissions: contents: write strategy: fail-fast: false matrix: settings: - host: macos-latest target: x86_64-apple-darwin os: darwin arch: amd64 cli_only: false - host: macos-latest target: aarch64-apple-darwin os: darwin arch: arm64 cli_only: false # The WIX version we use for the installer (latest 3.something) doesn't support arm builds - if we need to support arm windows, # we'd need to switch the installer toolchain to WIX 4.xx, not sure how that works out with tauri # - host: windows-latest # target: aarch64-pc-windows-msvc # arch: arm64 # cli-only: false - host: windows-latest target: x86_64-pc-windows-msvc arch: amd64 cli_only: false - host: ubuntu-22.04 target: x86_64-unknown-linux-gnu os: linux arch: amd64 cli_only: false - host: ubuntu-22.04 target: aarch64-unknown-linux-gnu os: linux arch: arm64 cli_only: true name: ${{ matrix.settings.target }} runs-on: ${{ matrix.settings.host }} env: GO111MODULE: on GOFLAGS: -mod=vendor steps: - name: Set git to use LF run: | git config --global core.autocrlf false git config --global core.eol lf - name: Checkout repository uses: actions/checkout@v3 - name: Apply Version if: matrix.settings.cli_only == false run: yarn version --new-version ${{ needs.create-release.outputs.package_version }} --no-git-tag-version working-directory: "./desktop" - name: Setup System Dependencies if: matrix.settings.host == 'ubuntu-22.04' && matrix.settings.cli_only == false run: | sudo apt-get update sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libayatana-appindicator3-dev librsvg2-dev - name: Rust setup uses: dtolnay/rust-toolchain@stable if: matrix.settings.cli_only == false with: targets: ${{ matrix.settings.target }} - name: Rust cache uses: swatinem/rust-cache@v2 if: matrix.settings.cli_only == false with: workspaces: "./desktop/src-tauri -> target" - name: Go setup uses: actions/setup-go@v2 with: go-version: 1.21.8 - name: Build Sidecar CLI if: matrix.settings.host != 'windows-latest' run: | BIN_NAME=devpod-cli-${{ matrix.settings.target }} GOOS=${{ matrix.settings.os }} GOARCH=${{ matrix.settings.arch }} CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/loft-sh/devpod/pkg/version.version="v${{ needs.create-release.outputs.original_package_version }}" -X github.com/loft-sh/devpod/pkg/telemetry.telemetryPrivateKey=${{ secrets.DEVPOD_TELEMETRY_PRIVATE_KEY }} -X github.com/loft-sh/devpod/pkg/devcontainer/crane.craneSigningKey="${{ secrets.CRANE_PRIVATE_KEY }}"" -o "test/$BIN_NAME" cp "test/$BIN_NAME" "desktop/src-tauri/bin/$BIN_NAME" ls desktop/src-tauri/bin - name: Build Sidecar CLI if: matrix.settings.host == 'windows-latest' shell: cmd run: | set GOOS=windows set GOARCH=${{ matrix.settings.arch }} set BIN_NAME=devpod-cli-${{ matrix.settings.target }}.exe go build -ldflags "-s -w -X github.com/loft-sh/devpod/pkg/version.version="v${{ needs.create-release.outputs.original_package_version }}" -X github.com/loft-sh/devpod/pkg/telemetry.telemetryPrivateKey=${{ secrets.DEVPOD_TELEMETRY_PRIVATE_KEY }}" -o "test\%BIN_NAME%" xcopy /F /Y "test\%BIN_NAME%" desktop\src-tauri\bin\* - name: Sync node version and setup cache uses: actions/setup-node@v3 if: matrix.settings.cli_only == false with: node-version: "lts/*" cache: "yarn" cache-dependency-path: "./desktop/yarn.lock" - name: Install frontend dependencies if: matrix.settings.cli_only == false run: yarn install working-directory: "./desktop" - name: Install additional ubuntu dependencies if: matrix.settings.host == 'ubuntu-22.04' run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev librsvg2-dev patchelf - name: Build Desktop App if: matrix.settings.host == 'ubuntu-22.04' && matrix.settings.cli_only == false uses: tauri-apps/tauri-action@v0.5.15 with: releaseId: ${{ needs.create-release.outputs.release_id }} projectPath: "./desktop" args: "--config src-tauri/tauri-flatpak.conf.json --target ${{ matrix.settings.target }} --bundles appimage,deb,updater" includeUpdaterJson: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} # AppImage Signing: SIGN: ${{ secrets.APP_IMAGE_SIGN }} SIGN_KEY: ${{ secrets.APP_IMAGE_SIGN_KEY }} APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.APP_IMAGE_SIGN_PASSPHRASE }} - name: Build Desktop App if: matrix.settings.host == 'macos-latest' && matrix.settings.cli_only == false uses: tauri-apps/tauri-action@v0.5.15 with: releaseId: ${{ needs.create-release.outputs.release_id }} projectPath: "./desktop" args: "--target ${{ matrix.settings.target }}" includeUpdaterJson: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} # MacOS Signing: ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} CI: false # https://github.com/tauri-apps/tauri-action/issues/740 - name: Build linux tar.gz if: matrix.settings.host == 'ubuntu-22.04' && matrix.settings.cli_only == false id: build-desktop-targz run: | cd ./desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/appimage/DevPod.AppDir || exit 1 tar --exclude=usr/bin/xdg-open --exclude=usr/lib --exclude=usr/share/doc --exclude=usr/share/glib-2.0 -zcvf DevPod-desktop.tar.gz usr mv DevPod-desktop.tar.gz ../../DevPod-${{needs.create-release.outputs.package_version}}.tar.gz - name: Prepare Windows Signing Tool if: matrix.settings.host == 'windows-latest' && matrix.settings.cli_only == false run: | $destination_path = "$Env:USERPROFILE\code-signing" $download_url = "$Env:CODESIGNTOOL_DOWNLOAD_URL" # Create the directory if it doesn't exist New-Item -ItemType Directory -Force -Path $destination_path | Out-Null Write-Output "Starting to download CodeSignTool from $download_url" Invoke-WebRequest -Uri $download_url -OutFile codesigntool.zip Write-Output "Unzipping codesigntool.zip to $destination_path" Expand-Archive "codesigntool.zip" -DestinationPath $destination_path Add-Content -Path $env:GITHUB_PATH -Value $destination_path # This is requried in addition to setting the PATH because of how CodeSignTool wrote their batch script "CODE_SIGN_TOOL_PATH=$destination_path" | Out-File -FilePath $env:GITHUB_ENV -Append env: CODESIGNTOOL_DOWNLOAD_URL: ${{ vars.CODESIGNTOOL_DOWNLOAD_URL }} - name: Print Signing Tool Version if: matrix.settings.host == 'windows-latest' && matrix.settings.cli_only == false run: | Write-Output "Attempting to get CodeSignTool version" CodeSignTool.bat --version - name: Build Desktop App if: matrix.settings.host == 'windows-latest' && matrix.settings.cli_only == false id: build-desktop-app uses: tauri-apps/tauri-action@v0.5.15 with: projectPath: "./desktop" args: " --target ${{ matrix.settings.target }}" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} CODESIGNTOOL_USERNAME: ${{ secrets.CODESIGNTOOL_USERNAME }} CODESIGNTOOL_PASSWORD: ${{ secrets.CODESIGNTOOL_PASSWORD }} CODESIGNTOOL_TOTP_SECRET: ${{ secrets.CODESIGNTOOL_TOTP_SECRET }} CODESIGNTOOL_CREDENTIAL_ID: ${{ secrets.CODESIGNTOOL_CREDENTIAL_ID }} - name: Sign Windows Sidecar Binary if: matrix.settings.host == 'windows-latest' shell: powershell env: CODESIGNTOOL_USERNAME: ${{ secrets.CODESIGNTOOL_USERNAME }} CODESIGNTOOL_PASSWORD: ${{ secrets.CODESIGNTOOL_PASSWORD }} CODESIGNTOOL_TOTP_SECRET: ${{ secrets.CODESIGNTOOL_TOTP_SECRET }} CODESIGNTOOL_CREDENTIAL_ID: ${{ secrets.CODESIGNTOOL_CREDENTIAL_ID }} run: | $username = "$Env:CODESIGNTOOL_USERNAME" $password = "$Env:CODESIGNTOOL_PASSWORD" $totp_secret = "$Env:CODESIGNTOOL_TOTP_SECRET" $credential_id = "$Env:CODESIGNTOOL_CREDENTIAL_ID" $cli_input_file_path = "desktop\src-tauri\bin\devpod-cli-${{ matrix.settings.target }}.exe" Write-Output "Signing files" $cli_input_file_path = Resolve-Path "$cli_input_file_path" | select -ExpandProperty Path CodeSignTool.bat sign -username="$username" -password="$password" -totp_secret="$totp_secret" -credential_id="$credential_id" -input_file_path="$cli_input_file_path" -override - name: Upload Release Asset if: matrix.settings.host == 'windows-latest' uses: actions/github-script@v6 with: script: | const fs = require("fs") const version = "${{ needs.create-release.outputs.package_version }}" // prepare MSI vars const msiName = `DevPod_${version}_x64_en-US.msi` const msiPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/msi/${msiName}` const msiZipName = `${msiName}.zip` const msiZipPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/msi/${msiZipName}` const msiZipSigName = `${msiName}.zip.sig` const msiZipSigPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/msi/${msiZipSigName}` // prepare NSIS vars // the installer itself is suffixed with `.exe` but updater artifacts end with `.nsis.*` const nsisName = `DevPod_${version}_x64-setup` const nsisPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/nsis/${nsisName}.exe` // Let's skip uploading the updater artifacts until we've figured out auto updating for both nsis and msi // const nsisZipName = `${nsisName}.nsis.zip` // const nsisZipPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/nsis/${nsisZipName}` // const nsisZipSigName = `${nsisName}.nsis.zip.sig` // const nsisZipSigPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/nsis/${nsisZipSigName}` const cliName = "devpod-windows-${{ matrix.settings.arch }}.exe" const cliPath = "desktop/src-tauri/bin/devpod-cli-${{ matrix.settings.target }}.exe" const releaseId = "${{ needs.create-release.outputs.release_id }}" const releaseAssets = [ { name: cliName, path: cliPath }, { name: msiName, path: msiPath }, { name: msiZipName, path: msiZipPath }, { name: msiZipSigName, path: msiZipSigPath }, { name: `${nsisName}.exe`, path: nsisPath }, // { name: nsisZipName, path: nsisZipPath }, // { name: nsisZipSigName, path: nsisZipSigPath }, ] for (const asset of releaseAssets) { console.log("Attempting to upload release asset: ", asset) await github.rest.repos.uploadReleaseAsset({ headers: { "content-type": "application/zip", "content-length": fs.statSync(asset.path).size }, name: asset.name, data: fs.readFileSync(asset.path), owner: context.repo.owner, repo: context.repo.repo, release_id: releaseId }) } env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload CLI Asset if: matrix.settings.host != 'windows-latest' uses: actions/github-script@v6 with: script: | const fs = require("fs") const releaseId = "${{ needs.create-release.outputs.release_id }}" const assetName = "devpod-${{ matrix.settings.os }}-${{ matrix.settings.arch }}" const assetPath = "desktop/src-tauri/bin/devpod-cli-${{ matrix.settings.target }}" console.log("Attempting to upload release asset: ", assetName) await github.rest.repos.uploadReleaseAsset({ headers: { "content-type": "application/zip", "content-length": fs.statSync(assetPath).size }, name: assetName, data: fs.readFileSync(assetPath), owner: context.repo.owner, repo: context.repo.repo, release_id: releaseId }) env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload Tar.gz Asset if: matrix.settings.host == 'ubuntu-22.04' && matrix.settings.cli_only == false uses: actions/github-script@v6 with: script: | const fs = require("fs") const releaseId = "${{ needs.create-release.outputs.release_id }}" const assetName = "DevPod-${{needs.create-release.outputs.package_version}}.tar.gz" const assetPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/${assetName}` console.log("Attempting to upload release asset: ", assetName) await github.rest.repos.uploadReleaseAsset({ headers: { "content-type": "application/zip", "content-length": fs.statSync(assetPath).size }, name: assetName, data: fs.readFileSync(assetPath), owner: context.repo.owner, repo: context.repo.repo, release_id: releaseId }) env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload flatpak assets if: matrix.settings.host == 'ubuntu-22.04' && matrix.settings.cli_only == false uses: actions/github-script@v6 with: script: | const fs = require("fs") const releaseId = "${{ needs.create-release.outputs.release_id }}" const assetName = "DevPod.desktop" const assetPath = `desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/deb/DevPod_${{needs.create-release.outputs.package_version}}_amd64/data/usr/share/applications/${assetName}` console.log("Attempting to upload release asset: ", assetName) await github.rest.repos.uploadReleaseAsset({ headers: { "content-type": "application/zip", "content-length": fs.statSync(assetPath).size }, name: assetName, data: fs.readFileSync(assetPath), owner: context.repo.owner, repo: context.repo.repo, release_id: releaseId }) const mdAssetName = "DevPod.metainfo.xml" const mdAssetPath = `desktop/flatpak/${mdAssetName}` console.log("Attempting to upload release asset: ", mdAssetName) await github.rest.repos.uploadReleaseAsset({ headers: { "content-type": "application/zip", "content-length": fs.statSync(mdAssetPath).size }, name: mdAssetName, data: fs.readFileSync(mdAssetPath), owner: context.repo.owner, repo: context.repo.repo, release_id: releaseId }) env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Generate updated flatpak manifest if: matrix.settings.host == 'ubuntu-22.04' && matrix.settings.cli_only == false run: | export VERSION="${{needs.create-release.outputs.package_version}}" export debPath="desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/deb/DevPod_${{needs.create-release.outputs.package_version}}_amd64.deb" export desktopPath="desktop/src-tauri/target/${{ matrix.settings.target }}/release/bundle/deb/DevPod_${{needs.create-release.outputs.package_version}}_amd64/data/usr/share/applications/${assetName}/DevPod.desktop" export metaPath="desktop/flatpak/DevPod.metainfo.xml" export SHA256=$(sha256sum "${debPath}" | cut -f1 -d ' ') export DESKTOP_SHA256=$(sha256sum "${desktopPath}" | cut -f1 -d ' ') export META_SHA256=$(sha256sum "${metaPath}" | cut -f1 -d ' ') envsubst < desktop/flatpak/sh.loft.devpod.tmpl > desktop/flatpak/sh.loft.devpod.yaml # - name: Updates flatpak manifest in flathub # uses: dmnemec/copy_file_to_another_repo_action@master # env: # API_TOKEN_GITHUB: ${{ secrets.API_TOKEN_GITHUB }} # with: # source_file: 'desktop/flatpak/sh.loft.DevPod.yaml' # destination_repo: 'flathub/sh.loft.DevPod' # destination_folder: '' # user_email: 'bot@loft.sh' # user_name: 'loftbot' # commit_message: 'Update flatpak manifest with latest release' publish-updates: needs: [build-app, create-release] if: startsWith(github.ref, 'refs/tags/v') == true permissions: contents: write runs-on: ubuntu-22.04 steps: - name: Checkout repository uses: actions/checkout@v3 - name: Release pro provider run: | set -e VERSION="v${{ needs.create-release.outputs.original_package_version }}" OUT_DIR="release" ASSETS="devpod-darwin-amd64 devpod-darwin-arm64 devpod-linux-amd64 devpod-linux-arm64 devpod-windows-amd64.exe" echo "Prepare output directory $OUT_DIR..." if [ ! -d "$OUT_DIR" ]; then mkdir "$OUT_DIR" else rm -rf $OUT_DIR fi printf "Done\n\n" echo "Download release assets into $OUT_DIR..." for asset in $ASSETS; do printf "\t$asset\n" gh release download $VERSION --pattern="$asset" --dir="$OUT_DIR" printf "\tDone\n" done printf "Done\n\n" echo "Generate provider.yaml..." go run ./hack/pro/main.go $VERSION > ./release/provider.yaml printf "Done\n\n" echo "Upload provider.yaml..." gh release upload $VERSION ./release/provider.yaml --clobber printf "Done\n\n" env: GH_TOKEN: ${{ github.token }} - name: Update `latest.json` uses: actions/github-script@v6 with: retries: 2 retry-exempt-status-codes: 400,401,403 script: | // At this point, we should have `linux-x86_64`, `darwin-aarch64` and `darwin-x86_64`. // We need to add the missing platform/arch combinations by hand const fs = require("fs") async function fetchAsset(assetID) { const releaseAsset = await github.rest.repos.getReleaseAsset({ owner: context.repo.owner, repo: context.repo.repo, asset_id: assetID, headers: { accept: "application/octet-stream" } }) const res = await fetch(releaseAsset.url, { headers: { accept: "application/octet-stream" } }) if (!res.ok) { core.setFailed(`${await res.text()}`) } return res } const releaseId = "${{ needs.create-release.outputs.release_id }}" const releaseArgs = { owner: context.repo.owner, repo: context.repo.repo, release_id: releaseId } const release = await github.rest.repos.getRelease({ ...releaseArgs }) const latestAsset = release.data.assets.find(a => a.name === "latest.json") core.info(`Downloading ${latestAsset.name} (ID: ${latestAsset.id})`) const latestRes = await fetchAsset(latestAsset.id) const latest = await latestRes.json() const version = latest.version const infos = [ { target: "linux-x86_64", sigFile: ".AppImage.tar.gz.sig", packageType: ".tar.gz", originalAssetName: `DevPod_${version}_amd64.AppImage`, desiredAssetName: "DevPod_linux_amd64.AppImage" }, { target: "darwin-aarch64", sigFile: "aarch64.app.tar.gz.sig", packageType: ".tar.gz", originalAssetName: `DevPod_${version}_aarch64.dmg`, desiredAssetName: "DevPod_macos_aarch64.dmg", originalUpdaterAssetName: "DevPod_aarch64.app.tar.gz", desiredUpdaterAssetName: "DevPod_macos_aarch64.app.tar.gz" }, { target: "darwin-x86_64", sigFile: "x64.app.tar.gz.sig", packageType: ".tar.gz", originalAssetName: `DevPod_${version}_x64.dmg`, desiredAssetName: "DevPod_macos_x64.dmg", originalUpdaterAssetName: "DevPod_x64.app.tar.gz", desiredUpdaterAssetName: "DevPod_macos_x64.app.tar.gz" }, { target: "windows-x86_64", sigFile: ".msi.zip.sig", packageType: ".zip", originalAssetName: `DevPod_${version}_x64_en-US.msi`, desiredAssetName: "DevPod_windows_x64_en-US.msi" }, { originalAssetName: `DevPod-${version}.tar.gz`, desiredAssetName: "DevPod_linux_x86_64.tar.gz" }, ] for (const info of infos) { // Update latest.json for platform if (info.target) { core.info(`Generating update info for ${info.desiredAssetName}`) const sigAsset = release.data.assets.find(a => a.name.endsWith(info.sigFile)) if (!sigAsset) { core.warning(`Unable to find sig asset: ${info.sigFile}`) continue } core.info(`Downloading ${sigAsset.name} (ID: ${sigAsset.id})`) const sig = await fetchAsset(sigAsset.id) let assetName = `${info.desiredAssetName}${info.packageType}` if (info.desiredUpdaterAssetName) { assetName = info.desiredUpdaterAssetName } latest.platforms[info.target] = { signature: await sig.text(), url: `https://github.com/loft-sh/devpod/releases/download/${process.env.GITHUB_REF_NAME}/${assetName}`, } // once we're done with the sig file, delete it await github.rest.repos.deleteReleaseAsset({ ...releaseArgs, asset_id: sigAsset.id }) } const a = release.data.assets.find(a => a.name === info.originalAssetName) if (!a) { core.warning(`Unable to find asset: ${info.originalAssetName}`) continue } const assetID = a.id // Update the asset name await github.rest.repos.updateReleaseAsset({ owner: context.repo.owner, repo: context.repo.repo, asset_id: assetID, name: info.desiredAssetName }) if (info.packageType) { let name = `${info.originalAssetName}${info.packageType}` if (info.originalUpdaterAssetName) { name = info.originalUpdaterAssetName } const b = release.data.assets.find(a => a.name === name) if (!b) { core.warning(`Unable to find update asset: ${name}`) continue } let desiredName = `${info.desiredAssetName}${info.packageType}` if (info.desiredUpdaterAssetName) { desiredName = info.desiredUpdaterAssetName } const assetID = b.id // Update the asset name await github.rest.repos.updateReleaseAsset({ owner: context.repo.owner, repo: context.repo.repo, asset_id: assetID, name: desiredName }) } } const latestJSON = JSON.stringify(latest) const latestDestPath = "desktop/latest.json" core.info(`Writing latest.json to disk (${latestDestPath}): ${latestJSON}`) fs.writeFileSync(latestDestPath, latestJSON) // Attempting to upload a previously released asset results in an error so we need to clean up before if (latestAsset) { await github.rest.repos.deleteReleaseAsset({ ...releaseArgs, asset_id: latestAsset.id }) } await github.rest.repos.uploadReleaseAsset({ ...releaseArgs, headers: { "content-type": "application/zip", "content-length": fs.statSync(latestDestPath).size }, name: "latest.json", data: fs.readFileSync(latestDestPath), }) ================================================ FILE: .github/workflows/stale.yaml ================================================ name: Close inactive issues on: schedule: - cron: "30 1 * * *" jobs: close-issues: runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - uses: actions/stale@v5 with: operations-per-run: 60 days-before-issue-stale: 60 days-before-issue-close: 30 stale-issue-label: stale stale-issue-message: | This issue is stale because it has been open for 60 days with no activity. It will be closed in 30 days on inactivity. close-issue-message: | This issue was closed because it has been inactive for 30 days since being marked as stale. days-before-pr-stale: -1 days-before-pr-close: -1 repo-token: ${{ secrets.GITHUB_TOKEN }} exempt-all-assignees: true exempt-issue-labels: >- in-progress, assigned, kind/bug, kind/enhancement ================================================ FILE: .github/workflows/ui-ci.yaml ================================================ name: UI CI on: workflow_dispatch: {} pull_request: branches: - main - release-* paths: - desktop/ jobs: check: runs-on: ubuntu-latest defaults: run: working-directory: ./desktop steps: - name: checkout uses: actions/checkout@v4 - name: setup node uses: actions/setup-node@v4 with: node-version: "20" - name: install dependencies run: yarn install --frozen-lockfile - name: lint run: yarn lint:ci - name: check format run: yarn format:check - name: check types run: yarn types:check # This only builds the frontend assets, not the full DevPod Desktop app to make it quicker - name: build run: yarn build ================================================ FILE: .github/workflows/unit-tests.yaml ================================================ name: Unit tests on: workflow_dispatch: {} pull_request: types: - opened - reopened - synchronize - edited branches: - main paths: - "**.go" - "hack/unit-tests.sh" - ".github/workflows/unit-tests.yaml" - "!/docs/**" # make sure the pipeline is only running once concurrency: group: unit-${{ github.head_ref || github.ref_name }} cancel-in-progress: true jobs: unit-tests: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Set up Go uses: actions/setup-go@v3 with: go-version: 1.21.8 - name: Test run: ./hack/unit-tests.sh ================================================ FILE: .gitignore ================================================ /.idea .DS_Store /test /e2e/bin /devpod /devpod.exe /devpod-cli # Unit test targets /main /profile.out /package-lock.json /tags ================================================ FILE: .golangci.yaml ================================================ linters: # We want to gradually introduce new linters to the project disable-all: true enable: - asasalint - asciicheck - bidichk - decorder - durationcheck - errcheck - errname - errorlint - exhaustive - exportloopref - ginkgolinter - gocheckcompilerdirectives - goimports - gosimple - govet - grouper - importas - ineffassign - makezero - misspell - nakedret - promlinter - staticcheck - stylecheck - typecheck - tagalign - unconvert - unused - whitespace # - dupl # - cyclop # - funlen linters-settings: stylecheck: checks: ["*", "-ST1003"] exhaustive: check: - switch - map ignore-enum-types: "ResourceName|FileMode|ProgrammingLanguage" # - cyclop # - funlen issues: # Maximum issues count per one linter. Set to 0 to disable. Default is 50. max-issues-per-linter: 0 # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. max-same-issues: 0 ================================================ FILE: .vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Launch Package", "type": "go", "request": "launch", "mode": "auto", "program": "${workspaceRoot}", "args": "up examples/simple", } ] } ================================================ FILE: .vscode/settings.json ================================================ { "cSpell.words": [ "devpod", "perrors" ], "[mdx]": { "editor.wordWrap": "on" }, "rust-analyzer.linkedProjects": ["./desktop/src-tauri/Cargo.toml"], } ================================================ FILE: COMMUNITY.md ================================================ # Community Calls This repository contains the maintainer community calls from the DevPod Community. Join us on [Slack](https://slack.loft.sh/) if you have feedback, want to ask questions, or see something covered in the maintainers meeting. The complete Playlist is on [YouTube](https://youtube.com/playlist?list=PL8MSvTvMDqe5hZFjZX9lw1Ecq2xwWivp7&feature=shared). - [DevPod Maintainers Meeting : 03/07/2024](https://www.youtube.com/live/pYtQtQh6Zp4?si=DPqTYE1JfewMCBdm) - [DevPod Maintainers Meeting : 02/29/2024](https://www.youtube.com/live/btYC7VpJZws?si=nDBcMk2GDnI5Xtyo) - [DevPod Maintainers Meeting : 02/22/2024](https://youtu.be/2NmGUsj7LFw?feature=shared) - [DevPod Maintainers Meeting : 02/19/2024](https://youtu.be/-1EqOf8A-7c?feature=shared) Feel free to explore past discussions and catch up on what you might have missed. ## Reporting An Incident Please email hrittik@loft.sh to initiate an incident report. **Please do not make reports via our public slack channel.** ================================================ FILE: CONTRIBUTING.md ================================================ # Development ## Development Setup 1. Clone the repository locally 2. If you want to change something in DevPod agent code: 1. Exchange the URL in [DefaultAgentDownloadURL](./pkg/agent/agent.go) with a custom public repository release you have created. 2. Build devpod via: `./hack/rebuild.sh` 3. Upload `test/devpod-linux-amd64` and `test/devpod-linux-arm64` to the public repository release assets. 3. Build devpod via: `./hack/rebuild.sh` (asking for sudo password) 4. Add docker provider via `devpod provider add docker` 5. Configure docker provider via `devpod use provider docker` 6. Start devpod in vscode with `devpod up examples/simple` ## Build from source Prerequisites CLI: - [Go 1.20](https://go.dev/doc/install) Once installed, run `CGO_ENABLED=0 go build -ldflags "-s -w" -o devpod-cli` Prerequisites GUI: - [NodeJS + yarn](https://nodejs.org/en/) - [Rust](https://www.rust-lang.org/tools/install) - [Go](https://go.dev/doc/install) To build the app on Linux, you will need the following dependencies: ```bash sudo apt-get install libappindicator3-1 libgdk-pixbuf2.0-0 libbsd0 libxdmcp6 \ libwmf-0.2-7 libwmf-0.2-7-gtk libgtk-3-0 libwmf-dev libwebkit2gtk-4.0-37 \ librust-openssl-sys-dev librust-glib-sys-dev sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev \ libayatana-appindicator3-dev librsvg2-dev ``` Once installed, run - `cd desktop` - `yarn tauri build --config src-tauri/tauri-dev.conf.json` The application should be in `desktop/src-tauri/target/release` ## Provider Head over to [the docs](https://devpod.sh/docs/developing-providers/quickstart) for an introduction into developing your own providers ### Publish your provider Once you're provider is ready, update - `community.yaml` - `docs/pages/managing-providers/add-provider.mdx` to get your provider featured both in the documentation and the UI ## Deeplinks DevPod Desktop can handle deep links to perform various actions, like opening or importing workspaces. The scheme is: protocol: `devpod://` host: `command` searchParams: `foo=bar&fizz=buzz` resulting in a full url string of `devpod://command?foo=bar&fizz=buzz`. For more information, take a look at the indvidual command sections below. ### Open Workspace Open a workspace based on a workspace source. Similar to `devpod up`, but shareable host: `open` searchParams: `source` (required), `workspace`, `provider`, `ide` `devpod://open?source=your-url-encoded-source&workspace=my-workspace&provider=docker&ide=vscode` ### Import Workspace Import a remote DevPod.Pro workspace into your local client host: `import` searchParams: `workspace_id` (required), `workspace_uid` (required), `devpod_pro_host` (required), `options` ================================================ FILE: LICENSE ================================================ Mozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. ================================================ FILE: Makefile ================================================ GOOS := $(shell go env GOOS) GOARCH := $(shell go env GOARCH) SKIP_INSTALL := false # Platform host PLATFORM_HOST := localhost:8080 # Build the CLI and Desktop .PHONY: build build: SKIP_INSTALL=$(SKIP_INSTALL) BUILD_PLATFORMS=$(GOOS) BUILD_ARCHS=$(GOARCH) ./hack/rebuild.sh # Run the desktop app .PHONY: run-desktop run-desktop: build cd desktop && yarn desktop:dev:debug # Run the daemon against loft host .PHONY: run-daemon run-daemon: build devpod pro daemon start --host $(PLATFORM_HOST) # Namespace to use for the platform NAMESPACE := loft # Copy the devpod binary to the platform pod .PHONY: cp-to-platform cp-to-platform: SKIP_INSTALL=true BUILD_PLATFORMS=linux BUILD_ARCHS=$(GOARCH) ./hack/rebuild.sh POD=$$(kubectl get pod -n $(NAMESPACE) -l app=loft,release=loft -o jsonpath='{.items[0].metadata.name}'); \ echo "Copying ./test/devpod-linux-$(GOARCH) to pod $$POD"; \ kubectl cp -n $(NAMESPACE) ./test/devpod-linux-$(GOARCH) $$POD:/usr/local/bin/devpod ================================================ FILE: README.md ================================================
DevPod wordmark ### **[Website](https://www.devpod.sh)** • **[Quickstart](https://www.devpod.sh/docs/getting-started/install)** • **[Documentation](https://www.devpod.sh/docs/what-is-devpod)** • **[Blog](https://loft.sh/blog)** • **[𝕏 (Twitter)](https://x.com/loft_sh)** • **[Slack](https://slack.loft.sh/)** [![Join us on Slack!](docs/static/media/slack.svg)](https://slack.loft.sh/) [![Open in DevPod!](https://devpod.sh/assets/open-in-devpod.svg)](https://devpod.sh/open#https://github.com/loft-sh/devpod) **[We are hiring!](https://www.loft.sh/careers) Come build the future of remote development environments with us.** DevPod is a client-only tool to create reproducible developer environments based on a [devcontainer.json](https://containers.dev/) on any backend. Each developer environment runs in a container and is specified through a [devcontainer.json](https://containers.dev/). Through DevPod providers, these environments can be created on any backend, such as the local computer, a Kubernetes cluster, any reachable remote machine, or in a VM in the cloud. ![Codespaces](docs/static/media/codespaces-but.png) You can think of DevPod as the glue that connects your local IDE to a machine where you want to develop. So depending on the requirements of your project, you can either create a workspace locally on the computer, on a beefy cloud machine with many GPUs, or a spare remote computer. Within DevPod, every workspace is managed the same way, which also makes it easy to switch between workspaces that might be hosted somewhere else. ![DevPod Flow](docs/static/media/devpod-flow.gif) ## Quickstart Download DevPod Desktop: - [MacOS Silicon/ARM](https://github.com/loft-sh/devpod/releases/latest/download/DevPod_macos_aarch64.dmg) - [MacOS Intel/AMD](https://github.com/loft-sh/devpod/releases/latest/download/DevPod_macos_x64.dmg) - [Windows](https://github.com/loft-sh/devpod/releases/latest/download/DevPod_windows_x64_en-US.msi) - [Linux AppImage](https://github.com/loft-sh/devpod/releases/latest/download/DevPod_linux_amd64.AppImage) Take a look at the [DevPod Docs](https://devpod.sh/docs/getting-started/install) for more information. ## Why DevPod? DevPod reuses the open [DevContainer standard](https://containers.dev/) (used by GitHub Codespaces and VSCode DevContainers) to create a consistent developer experience no matter what backend you want to use. Compared to hosted services such as Github Codespaces, JetBrains Spaces, or Google Cloud Workstations, DevPod has the following advantages: * **Cost savings**: DevPod is usually around 5-10 times cheaper than existing services with comparable feature sets because it uses bare virtual machines in any cloud and shuts down unused virtual machines automatically. * **No vendor lock-in**: Choose whatever cloud provider suits you best, be it the cheapest one or the most powerful, DevPod supports all cloud providers. If you are tired of using a provider, change it with a single command. * **Local development**: You get the same developer experience also locally, so you don't need to rely on a cloud provider at all. * **Cross IDE support**: VSCode and the full JetBrains suite is supported, all others can be connected through simple ssh. * **Client-only**: No need to install a server backend, DevPod runs only on your computer. * **Open-Source**: DevPod is 100% open-source and extensible. A provider doesn't exist? Just create your own. * **Rich feature set**: DevPod already supports prebuilds, auto inactivity shutdown, git & docker credentials sync, and many more features to come. * **Desktop App**: DevPod comes with an easy-to-use desktop application that abstracts all the complexity away. If you want to build your own integration, DevPod offers a feature-rich CLI as well. ================================================ FILE: SECURITY.md ================================================ # Security Policy We will disclose fixes for vulnerabilities in the release notes and urge you to upgrade once a new release is published. For upgrading guidelines, see [our official Upgrading Guideline](https://devpod.sh/docs/getting-started/update). **To receive update warnings in the CLI (as part of the terminal output whenever you run a command), you must use an official release binary as published on the [GitHub releases page](https://github.com/loft-sh/devpod/releases) of this project.** See the [install instructions for DevPod](https://devpod.sh/docs/getting-started/install) for the recommended methods of downloading an official release binary for your platform. Community maintained release binaries may **not** contain the version number and will therefore not be able to perform a version check. ## Reporting a Vulnerability Please report vulnerabilities to: [security@loft.sh](mailto:security@loft.sh) ================================================ FILE: cmd/agent/agent.go ================================================ package agent import ( "os" "github.com/loft-sh/devpod/cmd/agent/container" "github.com/loft-sh/devpod/cmd/agent/workspace" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/envfile" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var AgentExecutedAnnotation = "loft.sh/agent-executed" // NewAgentCmd returns a new root command func NewAgentCmd(globalFlags *flags.GlobalFlags) *cobra.Command { agentCmd := &cobra.Command{ Use: "agent", Short: "DevPod Agent", PersistentPreRunE: func(cobraCmd *cobra.Command, args []string) error { return AgentPersistentPreRunE(cobraCmd, args, globalFlags) }, Hidden: true, } agentCmd.AddCommand(workspace.NewWorkspaceCmd(globalFlags)) agentCmd.AddCommand(container.NewContainerCmd(globalFlags)) agentCmd.AddCommand(NewDaemonCmd(globalFlags)) agentCmd.AddCommand(NewContainerTunnelCmd(globalFlags)) agentCmd.AddCommand(NewGitCredentialsCmd(globalFlags)) agentCmd.AddCommand(NewGitSSHSignatureCmd(globalFlags)) agentCmd.AddCommand(NewGitSSHSignatureHelperCmd(globalFlags)) agentCmd.AddCommand(NewDockerCredentialsCmd(globalFlags)) return agentCmd } func AgentPersistentPreRunE(cobraCmd *cobra.Command, args []string, globalFlags *flags.GlobalFlags) error { // get top level parent parent := cobraCmd for parent.Parent() != nil { parent = parent.Parent() } if parent.Annotations == nil { parent.Annotations = map[string]string{} } parent.Annotations[AgentExecutedAnnotation] = "true" if globalFlags.LogOutput == "json" { log.Default.SetFormat(log.JSONFormat) } else { log.Default.MakeRaw() } if globalFlags.Silent { log.Default.SetLevel(logrus.FatalLevel) } else if globalFlags.Debug { log.Default.SetLevel(logrus.DebugLevel) } else if os.Getenv(clientimplementation.DevPodDebug) == "true" { log.Default.SetLevel(logrus.DebugLevel) } if globalFlags.DevPodHome != "" { _ = os.Setenv(config.DEVPOD_HOME, globalFlags.DevPodHome) } // apply environment envfile.Apply(log.Default.ErrorStreamOnly()) return nil } ================================================ FILE: cmd/agent/container/container.go ================================================ package container import ( "github.com/loft-sh/devpod/cmd/flags" "github.com/spf13/cobra" ) // NewContainerCmd returns a new command func NewContainerCmd(flags *flags.GlobalFlags) *cobra.Command { containerCmd := &cobra.Command{ Use: "container", Short: "Container commands", } containerCmd.AddCommand(NewSetupContainerCmd(flags)) containerCmd.AddCommand(NewDaemonCmd()) containerCmd.AddCommand(NewVSCodeAsyncCmd()) containerCmd.AddCommand(NewOpenVSCodeAsyncCmd()) containerCmd.AddCommand(NewCredentialsServerCmd(flags)) containerCmd.AddCommand(NewSetupLoftPlatformAccessCmd(flags)) containerCmd.AddCommand(NewSSHServerCmd(flags)) return containerCmd } ================================================ FILE: cmd/agent/container/credentials_server.go ================================================ package container import ( "context" "encoding/base64" "encoding/json" "fmt" "net" "os" "strconv" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent/tunnel" "github.com/loft-sh/devpod/pkg/agent/tunnelserver" "github.com/loft-sh/devpod/pkg/credentials" "github.com/loft-sh/devpod/pkg/dockercredentials" "github.com/loft-sh/devpod/pkg/gitcredentials" "github.com/loft-sh/devpod/pkg/gitsshsigning" "github.com/loft-sh/devpod/pkg/netstat" portpkg "github.com/loft-sh/devpod/pkg/port" "github.com/loft-sh/log" "github.com/spf13/cobra" ) const ExitCodeIO int = 64 // CredentialsServerCmd holds the cmd flags type CredentialsServerCmd struct { *flags.GlobalFlags User string ConfigureGitHelper bool ConfigureDockerHelper bool ForwardPorts bool GitUserSigningKey string } // NewCredentialsServerCmd creates a new command func NewCredentialsServerCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &CredentialsServerCmd{ GlobalFlags: flags, } credentialsServerCmd := &cobra.Command{ Use: "credentials-server", Short: "Starts a credentials server", Args: cobra.NoArgs, RunE: func(c *cobra.Command, args []string) error { port, err := credentials.GetPort() if err != nil { return err } return cmd.Run(c.Context(), port) }, } credentialsServerCmd.Flags().BoolVar(&cmd.ConfigureGitHelper, "configure-git-helper", false, "If true will configure git helper") credentialsServerCmd.Flags().BoolVar(&cmd.ConfigureDockerHelper, "configure-docker-helper", false, "If true will configure docker helper") credentialsServerCmd.Flags().BoolVar(&cmd.ForwardPorts, "forward-ports", false, "If true will automatically try to forward open ports within the container") credentialsServerCmd.Flags().StringVar(&cmd.GitUserSigningKey, "git-user-signing-key", "", "") credentialsServerCmd.Flags().StringVar(&cmd.User, "user", "", "The user to use") _ = credentialsServerCmd.MarkFlagRequired("user") return credentialsServerCmd } // Run runs the command logic func (cmd *CredentialsServerCmd) Run(ctx context.Context, port int) error { // create a grpc client tunnelClient, err := tunnelserver.NewTunnelClient(os.Stdin, os.Stdout, true, ExitCodeIO) if err != nil { return fmt.Errorf("error creating tunnel client: %w", err) } // this message serves as a ping to the client _, err = tunnelClient.Ping(ctx, &tunnel.Empty{}) if err != nil { return fmt.Errorf("ping client: %w", err) } // create debug logger log := tunnelserver.NewTunnelLogger(ctx, tunnelClient, cmd.Debug) // forward ports if cmd.ForwardPorts { go func() { log.Debugf("Start watching & forwarding open ports") err = forwardPorts(ctx, tunnelClient, log) if err != nil { log.Errorf("error forwarding ports: %v", err) } }() } addr := net.JoinHostPort("localhost", strconv.Itoa(port)) if ok, err := portpkg.IsAvailable(addr); !ok || err != nil { log.Debugf("Port %d not available, exiting", port) return nil } // configure docker credential helper if cmd.ConfigureDockerHelper { err = dockercredentials.ConfigureCredentialsContainer(cmd.User, port, log) if err != nil { return err } } // configure git user err = configureGitUserLocally(ctx, cmd.User, tunnelClient) if err != nil { log.Debugf("Error configuring git user: %v", err) return err } // configure git credential helper if cmd.ConfigureGitHelper { binaryPath, err := os.Executable() if err != nil { return err } err = gitcredentials.ConfigureHelper(binaryPath, cmd.User, port) if err != nil { return fmt.Errorf("configure git helper: %w", err) } // cleanup when we are done defer func(userName string) { _ = gitcredentials.RemoveHelper(userName) }(cmd.User) } // configure git ssh signature helper if cmd.GitUserSigningKey != "" { decodedKey, err := base64.StdEncoding.DecodeString(cmd.GitUserSigningKey) if err != nil { return fmt.Errorf("decode git ssh signature key: %w", err) } err = gitsshsigning.ConfigureHelper(cmd.User, string(decodedKey), log) if err != nil { return fmt.Errorf("configure git ssh signature helper: %w", err) } // cleanup when we are done defer func(userName string) { _ = gitsshsigning.RemoveHelper(userName) }(cmd.User) } return credentials.RunCredentialsServer(ctx, port, tunnelClient, log) } func configureGitUserLocally(ctx context.Context, userName string, client tunnel.TunnelClient) error { // get local credentials localGitUser, err := gitcredentials.GetUser(userName) if err != nil { return err } else if localGitUser.Name != "" && localGitUser.Email != "" { return nil } // set user & email if not found response, err := client.GitUser(ctx, &tunnel.Empty{}) if err != nil { return fmt.Errorf("retrieve git user: %w", err) } // parse git user from response gitUser := &gitcredentials.GitUser{} err = json.Unmarshal([]byte(response.Message), gitUser) if err != nil { return fmt.Errorf("decode git user: %w", err) } // don't override what is already there if localGitUser.Name != "" { gitUser.Name = "" } if localGitUser.Email != "" { gitUser.Email = "" } // set git user err = gitcredentials.SetUser(userName, gitUser) if err != nil { return fmt.Errorf("set git user & email: %w", err) } return nil } func forwardPorts(ctx context.Context, client tunnel.TunnelClient, log log.Logger) error { return netstat.NewWatcher(&forwarder{ctx: ctx, client: client}, log).Run(ctx) } type forwarder struct { ctx context.Context client tunnel.TunnelClient } func (f *forwarder) Forward(port string) error { _, err := f.client.ForwardPort(f.ctx, &tunnel.ForwardPortRequest{Port: port}) return err } func (f *forwarder) StopForward(port string) error { _, err := f.client.StopForwardPort(f.ctx, &tunnel.StopForwardPortRequest{Port: port}) return err } ================================================ FILE: cmd/agent/container/daemon.go ================================================ package container import ( "context" "encoding/base64" "encoding/json" "fmt" "os" "os/exec" "os/signal" "strings" "sync" "syscall" "time" "github.com/loft-sh/devpod/pkg/agent" agentd "github.com/loft-sh/devpod/pkg/daemon/agent" "github.com/loft-sh/devpod/pkg/devcontainer/config" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/ts" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) const ( RootDir = "/var/devpod" DaemonConfigPath = "/var/run/secrets/devpod/daemon_config" ) type DaemonCmd struct { Config *agentd.DaemonConfig Log log.Logger } // NewDaemonCmd creates the merged daemon command. func NewDaemonCmd() *cobra.Command { cmd := &DaemonCmd{ Config: &agentd.DaemonConfig{}, Log: log.NewStreamLogger(os.Stdout, os.Stderr, logrus.InfoLevel), } daemonCmd := &cobra.Command{ Use: "daemon", Short: "Starts the DevPod network daemon, SSH server and monitors container activity if timeout is set", Args: cobra.NoArgs, RunE: cmd.Run, } daemonCmd.Flags().StringVar(&cmd.Config.Timeout, "timeout", "", "The timeout to stop the container after") return daemonCmd } func (cmd *DaemonCmd) Run(c *cobra.Command, args []string) error { ctx := c.Context() errChan := make(chan error, 4) var wg sync.WaitGroup if err := cmd.loadConfig(); err != nil { return err } // Prepare timeout if specified. var timeoutDuration time.Duration if cmd.Config.Timeout != "" { var err error timeoutDuration, err = time.ParseDuration(cmd.Config.Timeout) if err != nil { return errors.Wrap(err, "failed to parse timeout duration") } if timeoutDuration > 0 { if err := setupActivityFile(); err != nil { return err } } } ctx, cancel := context.WithCancel(ctx) defer cancel() var tasksStarted bool // Start process reaper. if os.Getpid() == 1 { wg.Add(1) go runReaper(ctx, errChan, &wg) } // Start Tailscale networking server. if cmd.shouldRunNetworkServer() { tasksStarted = true wg.Add(1) go runNetworkServer(ctx, cmd, errChan, &wg) } // Start timeout monitor. if timeoutDuration > 0 { tasksStarted = true wg.Add(1) go runTimeoutMonitor(ctx, timeoutDuration, errChan, &wg) } // Start ssh server. if cmd.shouldRunSsh() { tasksStarted = true wg.Add(1) go runSshServer(ctx, cmd, errChan, &wg) } // In case no task is configured, just wait indefinitely. if !tasksStarted { wg.Add(1) go func() { defer wg.Done() <-ctx.Done() }() } // Listen for OS termination signals. go handleSignals(ctx, errChan) // Wait until an error (or termination signal) occurs. err := <-errChan cancel() wg.Wait() if err != nil { cmd.Log.Errorf("Daemon error: %v", err) os.Exit(1) } os.Exit(0) return nil // Unreachable but needed. } // loadConfig loads the daemon configuration from base64-encoded JSON. // If a CLI-provided timeout exists, it will override the timeout in the config. func (cmd *DaemonCmd) loadConfig() error { // check local file encodedCfg := "" configBytes, err := os.ReadFile(DaemonConfigPath) if err != nil { if errors.Is(err, os.ErrNotExist) { // check environment variable encodedCfg = os.Getenv(config.WorkspaceDaemonConfigExtraEnvVar) } else { return fmt.Errorf("get daemon config file %s: %w", DaemonConfigPath, err) } } else { encodedCfg = string(configBytes) } if strings.TrimSpace(encodedCfg) != "" { decoded, err := base64.StdEncoding.DecodeString(encodedCfg) if err != nil { return fmt.Errorf("error decoding daemon config: %w", err) } var cfg agentd.DaemonConfig if err = json.Unmarshal(decoded, &cfg); err != nil { return fmt.Errorf("error unmarshalling daemon config: %w", err) } if cmd.Config.Timeout != "" { cfg.Timeout = cmd.Config.Timeout } cmd.Config = &cfg } return nil } // shouldRunNetworkServer returns true if the required platform parameters are present. func (cmd *DaemonCmd) shouldRunNetworkServer() bool { return cmd.Config.Platform.AccessKey != "" && cmd.Config.Platform.PlatformHost != "" && cmd.Config.Platform.WorkspaceHost != "" } // shouldRunSsh returns true if at least one SSH configuration value is provided. func (cmd *DaemonCmd) shouldRunSsh() bool { return cmd.Config.Ssh.Workdir != "" || cmd.Config.Ssh.User != "" } // setupActivityFile creates and sets permissions on the container activity file. func setupActivityFile() error { if err := os.WriteFile(agent.ContainerActivityFile, nil, 0777); err != nil { return err } return os.Chmod(agent.ContainerActivityFile, 0777) } // runReaper starts the process reaper and waits for context cancellation. func runReaper(ctx context.Context, errChan chan<- error, wg *sync.WaitGroup) { defer wg.Done() agentd.RunProcessReaper() <-ctx.Done() } // runTimeoutMonitor monitors the activity file and signals an error if the timeout is exceeded. func runTimeoutMonitor(ctx context.Context, duration time.Duration, errChan chan<- error, wg *sync.WaitGroup) { defer wg.Done() ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: stat, err := os.Stat(agent.ContainerActivityFile) if err != nil { continue } if !stat.ModTime().Add(duration).After(time.Now()) { errChan <- errors.New("timeout reached, terminating daemon") return } } } } // runNetworkServer starts the network server. func runNetworkServer(ctx context.Context, cmd *DaemonCmd, errChan chan<- error, wg *sync.WaitGroup) { defer wg.Done() if err := os.MkdirAll(RootDir, os.ModePerm); err != nil { errChan <- err return } logger := initLogging() config := client.NewConfig() config.AccessKey = cmd.Config.Platform.AccessKey config.Host = "https://" + cmd.Config.Platform.PlatformHost config.Insecure = true baseClient := client.NewClientFromConfig(config) if err := baseClient.RefreshSelf(ctx); err != nil { errChan <- fmt.Errorf("failed to refresh client: %w", err) return } tsServer := ts.NewWorkspaceServer(&ts.WorkspaceServerConfig{ AccessKey: cmd.Config.Platform.AccessKey, PlatformHost: ts.RemoveProtocol(cmd.Config.Platform.PlatformHost), WorkspaceHost: cmd.Config.Platform.WorkspaceHost, Client: baseClient, RootDir: RootDir, LogF: func(format string, args ...interface{}) { logger.Infof(format, args...) }, }, logger) if err := tsServer.Start(ctx); err != nil { errChan <- fmt.Errorf("network server: %w", err) } } // runSshServer starts the SSH server. func runSshServer(ctx context.Context, cmd *DaemonCmd, errChan chan<- error, wg *sync.WaitGroup) { defer wg.Done() binaryPath, err := os.Executable() if err != nil { errChan <- err return } args := []string{"agent", "container", "ssh-server"} if cmd.Config.Ssh.Workdir != "" { args = append(args, "--workdir", cmd.Config.Ssh.Workdir) } if cmd.Config.Ssh.User != "" { args = append(args, "--remote-user", cmd.Config.Ssh.User) } sshCmd := exec.Command(binaryPath, args...) sshCmd.Stdout = os.Stdout sshCmd.Stderr = os.Stderr if err := sshCmd.Start(); err != nil { errChan <- fmt.Errorf("failed to start SSH server: %w", err) return } done := make(chan struct{}) go func() { select { case <-ctx.Done(): if sshCmd.Process != nil { if err := sshCmd.Process.Signal(syscall.SIGTERM); err != nil { errChan <- fmt.Errorf("failed to send SIGTERM to SSH server: %w", err) } } case <-done: } }() if err := sshCmd.Wait(); err != nil { errChan <- fmt.Errorf("SSH server exited abnormally: %w", err) close(done) return } close(done) } // handleSignals listens for OS termination signals and sends an error through errChan. func handleSignals(ctx context.Context, errChan chan<- error) { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) select { case sig := <-sigChan: errChan <- fmt.Errorf("received signal: %v", sig) case <-ctx.Done(): } } // initLogging initializes logging and returns a combined logger. func initLogging() log.Logger { return log.NewStdoutLogger(nil, os.Stdout, os.Stderr, logrus.InfoLevel) } ================================================ FILE: cmd/agent/container/openvscode_async.go ================================================ package container import ( "encoding/json" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/compress" "github.com/loft-sh/devpod/pkg/devcontainer/config" "github.com/loft-sh/devpod/pkg/ide/openvscode" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // OpenVSCodeAsyncCmd holds the cmd flags type OpenVSCodeAsyncCmd struct { *flags.GlobalFlags SetupInfo string } // NewOpenVSCodeAsyncCmd creates a new command func NewOpenVSCodeAsyncCmd() *cobra.Command { cmd := &OpenVSCodeAsyncCmd{} vsCodeAsyncCmd := &cobra.Command{ Use: "openvscode-async", Short: "Starts openvscode", Args: cobra.NoArgs, RunE: cmd.Run, } vsCodeAsyncCmd.Flags().StringVar(&cmd.SetupInfo, "setup-info", "", "The container setup info") _ = vsCodeAsyncCmd.MarkFlagRequired("setup-info") return vsCodeAsyncCmd } // Run runs the command logic func (cmd *OpenVSCodeAsyncCmd) Run(_ *cobra.Command, _ []string) error { log.Default.Debugf("Start setting up container...") decompressed, err := compress.Decompress(cmd.SetupInfo) if err != nil { return err } setupInfo := &config.Result{} err = json.Unmarshal([]byte(decompressed), setupInfo) if err != nil { return err } // install IDE err = setupOpenVSCodeExtensions(setupInfo, log.Default) if err != nil { return err } return nil } func setupOpenVSCodeExtensions(setupInfo *config.Result, log log.Logger) error { vsCodeConfiguration := config.GetVSCodeConfiguration(setupInfo.MergedConfig) user := config.GetRemoteUser(setupInfo) return openvscode.NewOpenVSCodeServer(vsCodeConfiguration.Extensions, "", user, "", "", nil, log).InstallExtensions() } ================================================ FILE: cmd/agent/container/setup.go ================================================ //go:build !windows package container import ( "context" "crypto/tls" "encoding/json" "fmt" "io" "net/http" "net/url" "os" "os/exec" "path/filepath" "runtime" "strconv" "strings" "time" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" "github.com/loft-sh/devpod/pkg/agent/tunnel" "github.com/loft-sh/devpod/pkg/agent/tunnelserver" "github.com/loft-sh/devpod/pkg/command" "github.com/loft-sh/devpod/pkg/compress" config2 "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/copy" "github.com/loft-sh/devpod/pkg/credentials" "github.com/loft-sh/devpod/pkg/devcontainer/config" "github.com/loft-sh/devpod/pkg/devcontainer/setup" "github.com/loft-sh/devpod/pkg/dockercredentials" "github.com/loft-sh/devpod/pkg/envfile" "github.com/loft-sh/devpod/pkg/extract" "github.com/loft-sh/devpod/pkg/git" "github.com/loft-sh/devpod/pkg/ide/fleet" "github.com/loft-sh/devpod/pkg/ide/jetbrains" "github.com/loft-sh/devpod/pkg/ide/jupyter" "github.com/loft-sh/devpod/pkg/ide/openvscode" "github.com/loft-sh/devpod/pkg/ide/rstudio" "github.com/loft-sh/devpod/pkg/ide/vscode" provider2 "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/single" "github.com/loft-sh/devpod/pkg/ts" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var DockerlessImageConfigOutput = "/.dockerless/image.json" // SetupContainerCmd holds the cmd flags type SetupContainerCmd struct { *flags.GlobalFlags ChownWorkspace bool StreamMounts bool InjectGitCredentials bool ContainerWorkspaceInfo string SetupInfo string AccessKey string PlatformHost string WorkspaceHost string } // NewSetupContainerCmd creates a new command func NewSetupContainerCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &SetupContainerCmd{ GlobalFlags: flags, } setupContainerCmd := &cobra.Command{ Use: "setup", Short: "Sets up a container", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background()) }, } setupContainerCmd.Flags().BoolVar(&cmd.StreamMounts, "stream-mounts", false, "If true, will try to stream the bind mounts from the host") setupContainerCmd.Flags().BoolVar(&cmd.ChownWorkspace, "chown-workspace", false, "If DevPod should chown the workspace to the remote user") setupContainerCmd.Flags().BoolVar(&cmd.InjectGitCredentials, "inject-git-credentials", false, "If DevPod should inject git credentials during setup") setupContainerCmd.Flags().StringVar(&cmd.ContainerWorkspaceInfo, "container-workspace-info", "", "The container workspace info") setupContainerCmd.Flags().StringVar(&cmd.SetupInfo, "setup-info", "", "The container setup info") setupContainerCmd.Flags().StringVar(&cmd.AccessKey, "access-key", "", "Access Key to use") setupContainerCmd.Flags().StringVar(&cmd.WorkspaceHost, "workspace-host", "", "Workspace hostname to use") setupContainerCmd.Flags().StringVar(&cmd.PlatformHost, "platform-host", "", "Platform host") _ = setupContainerCmd.MarkFlagRequired("setup-info") return setupContainerCmd } // Run runs the command logic func (cmd *SetupContainerCmd) Run(ctx context.Context) error { // create a grpc client tunnelClient, err := tunnelserver.NewTunnelClient(os.Stdin, os.Stdout, true, 0) if err != nil { return fmt.Errorf("error creating tunnel client: %w", err) } // create debug logger logger := tunnelserver.NewTunnelLogger(ctx, tunnelClient, cmd.Debug) logger.Debugf("Created logger") // this message serves as a ping to the client _, err = tunnelClient.Ping(ctx, &tunnel.Empty{}) if err != nil { return errors.Wrap(err, "ping client") } // start setting up container logger.Debugf("Start setting up container...") workspaceInfo, _, err := agent.DecodeContainerWorkspaceInfo(cmd.ContainerWorkspaceInfo) if err != nil { return err } decompressed, err := compress.Decompress(cmd.SetupInfo) if err != nil { return err } setupInfo := &config.Result{} err = json.Unmarshal([]byte(decompressed), setupInfo) if err != nil { return err } // sync mounts if cmd.StreamMounts { mounts := config.GetMounts(setupInfo) logger.Debug("Syncing mounts... ", mounts) for _, m := range mounts { // If we are resetting the workspace and it's sources, always re stream the mounts if !workspaceInfo.CLIOptions.Reset { files, err := os.ReadDir(m.Target) if err == nil && len(files) > 0 { logger.Debug("Skip stream mount ", m.Target, " because it's not empty") continue } } // stream mount err = streamMount(ctx, workspaceInfo, m, tunnelClient, logger) if err != nil { return err } } } // do dockerless build err = dockerlessBuild(ctx, setupInfo, &workspaceInfo.Dockerless, tunnelClient, cmd.Debug, logger) if err != nil { return fmt.Errorf("dockerless build: %w", err) } // fill container env err = fillContainerEnv(setupInfo) if err != nil { return err } if cmd.InjectGitCredentials { // configure git credentials cancelCtx, cancel := context.WithCancel(ctx) defer cancel() cleanupFunc, err := configureSystemGitCredentials(cancelCtx, cancel, tunnelClient, logger) if err != nil { logger.Errorf("Error configuring git credentials: %v", err) } else { defer cleanupFunc() } } if b, err := workspaceInfo.PullFromInsideContainer.Bool(); err == nil && b { // check if workspace folder exists and is a git repository. // skip cloning if it does _, err := os.Stat(filepath.Join(setupInfo.SubstitutionContext.ContainerWorkspaceFolder, ".git")) if err == nil && !workspaceInfo.CLIOptions.Recreate { logger.Debugf("Workspace repository already checked out %s, skipping clone", setupInfo.SubstitutionContext.ContainerWorkspaceFolder) } else { if err := agent.CloneRepositoryForWorkspace(ctx, &workspaceInfo.Source, &workspaceInfo.Agent, setupInfo.SubstitutionContext.ContainerWorkspaceFolder, "", workspaceInfo.CLIOptions, true, logger, ); err != nil { return err } } } // setup container err = setup.SetupContainer(ctx, setupInfo, workspaceInfo.CLIOptions.WorkspaceEnv, cmd.ChownWorkspace, &workspaceInfo.CLIOptions.Platform, tunnelClient, logger) if err != nil { return err } // install IDE err = cmd.installIDE(setupInfo, &workspaceInfo.IDE, logger) if err != nil { return err } // start container daemon if necessary if !workspaceInfo.CLIOptions.Platform.Enabled && !workspaceInfo.CLIOptions.DisableDaemon && workspaceInfo.ContainerTimeout != "" { err = single.Single("devpod.daemon.pid", func() (*exec.Cmd, error) { logger.Debugf("Start DevPod Container Daemon with Inactivity Timeout %s", workspaceInfo.ContainerTimeout) binaryPath, err := os.Executable() if err != nil { return nil, err } return exec.Command(binaryPath, "agent", "container", "daemon", "--timeout", workspaceInfo.ContainerTimeout), nil }) if err != nil { return err } } out, err := json.Marshal(setupInfo) if err != nil { return fmt.Errorf("marshal setup info: %w", err) } _, err = tunnelClient.SendResult(ctx, &tunnel.Message{Message: string(out)}) if err != nil { return fmt.Errorf("send result: %w", err) } return nil } func fillContainerEnv(setupInfo *config.Result) error { // set remote-env if setupInfo.MergedConfig.RemoteEnv == nil { setupInfo.MergedConfig.RemoteEnv = make(map[string]string) } if _, ok := setupInfo.MergedConfig.RemoteEnv["PATH"]; !ok { setupInfo.MergedConfig.RemoteEnv["PATH"] = "${containerEnv:PATH}" } // merge config newMergedConfig := &config.MergedDevContainerConfig{} err := config.SubstituteContainerEnv(config.ListToObject(os.Environ()), setupInfo.MergedConfig, newMergedConfig) if err != nil { return errors.Wrap(err, "substitute container env") } setupInfo.MergedConfig = newMergedConfig return nil } func dockerlessBuild( ctx context.Context, setupInfo *config.Result, dockerlessOptions *provider2.ProviderDockerlessOptions, client tunnel.TunnelClient, debug bool, log log.Logger, ) error { if os.Getenv("DOCKERLESS") != "true" { return nil } _, err := os.Stat(DockerlessImageConfigOutput) if err == nil { log.Debugf("Skip dockerless build, because container was built already") return nil } buildContext := os.Getenv("DOCKERLESS_CONTEXT") if buildContext == "" { log.Debugf("Build context is missing for dockerless build") return nil } // check if build info is there fallbackDir := filepath.Join(config.DevPodDockerlessBuildInfoFolder, config.DevPodContextFeatureFolder) buildInfoDir := filepath.Join(buildContext, config.DevPodContextFeatureFolder) _, err = os.Stat(buildInfoDir) if err != nil { // try to rename from fallback dir err = copy.RenameDirectory(fallbackDir, buildInfoDir) if err != nil { return fmt.Errorf("rename dir: %w", err) } _, err = os.Stat(buildInfoDir) if err != nil { return fmt.Errorf("couldn't find build dir %s: %w", buildInfoDir, err) } } binaryPath, err := os.Executable() if err != nil { return err } // configure credentials if dockerlessOptions.DisableDockerCredentials != "true" { var cancel context.CancelFunc ctx, cancel = context.WithCancel(ctx) defer cancel() // configure the docker credentials dockerCredentialsDir, err := configureDockerCredentials(ctx, cancel, client, log) if err != nil { log.Errorf("Error configuring docker credentials: %v", err) } else { defer func() { _ = os.Unsetenv("DOCKER_CONFIG") _ = os.RemoveAll(dockerCredentialsDir) }() } } // build args args := []string{"build", "--ignore-path", binaryPath} args = append(args, parseIgnorePaths(dockerlessOptions.IgnorePaths)...) args = append(args, "--build-arg", "TARGETOS="+runtime.GOOS) args = append(args, "--build-arg", "TARGETARCH="+runtime.GOARCH) if dockerlessOptions.RegistryCache != "" { log.Debug("Appending registry cache to dockerless build arguments ", dockerlessOptions.RegistryCache) args = append(args, "--registry-cache", dockerlessOptions.RegistryCache) } // ignore mounts args = append(args, "--ignore-path", setupInfo.SubstitutionContext.ContainerWorkspaceFolder) for _, m := range setupInfo.MergedConfig.Mounts { // check if there already, then we don't touch it files, err := os.ReadDir(m.Target) if err == nil && len(files) > 0 { args = append(args, "--ignore-path", m.Target) } } // write output to log errWriter := log.Writer(logrus.InfoLevel, false) defer errWriter.Close() // start building log.Infof("Start dockerless building %s %s", "/.dockerless/dockerless", strings.Join(args, " ")) cmd := exec.CommandContext(ctx, "/.dockerless/dockerless", args...) if debug { debugWriter := log.Writer(logrus.DebugLevel, false) defer debugWriter.Close() cmd.Stdout = debugWriter } cmd.Stderr = errWriter cmd.Env = os.Environ() err = cmd.Run() if err != nil { return err } // add container env to envfile.json rawConfig, err := os.ReadFile(DockerlessImageConfigOutput) if err != nil { return err } // parse config file configFile := &v1.ConfigFile{} err = json.Unmarshal(rawConfig, configFile) if err != nil { return fmt.Errorf("parse container config: %w", err) } // apply env envfile.MergeAndApply(config.ListToObject(configFile.Config.Env), log) // rename build path _ = os.RemoveAll(fallbackDir) err = copy.RenameDirectory(buildInfoDir, fallbackDir) if err != nil { log.Debugf("Error renaming dir %s: %v", buildInfoDir, err) return nil } return nil } func parseIgnorePaths(ignorePaths string) []string { if strings.TrimSpace(ignorePaths) == "" { return nil } retPaths := []string{} splitted := strings.Split(ignorePaths, ",") for _, s := range splitted { retPaths = append(retPaths, "--ignore-path", strings.TrimSpace(s)) } return retPaths } func configureDockerCredentials( ctx context.Context, cancel context.CancelFunc, client tunnel.TunnelClient, log log.Logger, ) (string, error) { serverPort, err := credentials.StartCredentialsServer(ctx, cancel, client, log) if err != nil { return "", err } dockerCredentials, err := dockercredentials.ConfigureCredentialsDockerless("/.dockerless/.docker", serverPort, log) if err != nil { return "", err } return dockerCredentials, nil } func (cmd *SetupContainerCmd) installIDE(setupInfo *config.Result, ide *provider2.WorkspaceIDEConfig, log log.Logger) error { switch ide.Name { case string(config2.IDENone): return nil case string(config2.IDEVSCode): return cmd.setupVSCode(setupInfo, ide.Options, vscode.FlavorStable, log) case string(config2.IDEVSCodeInsiders): return cmd.setupVSCode(setupInfo, ide.Options, vscode.FlavorInsiders, log) case string(config2.IDECursor): return cmd.setupVSCode(setupInfo, ide.Options, vscode.FlavorCursor, log) case string(config2.IDEPositron): return cmd.setupVSCode(setupInfo, ide.Options, vscode.FlavorPositron, log) case string(config2.IDECodium): return cmd.setupVSCode(setupInfo, ide.Options, vscode.FlavorCodium, log) case string(config2.IDEWindsurf): return cmd.setupVSCode(setupInfo, ide.Options, vscode.FlavorWindsurf, log) case string(config2.IDEOpenVSCode): return cmd.setupOpenVSCode(setupInfo, ide.Options, log) case string(config2.IDEGoland): return jetbrains.NewGolandServer(config.GetRemoteUser(setupInfo), ide.Options, log).Install() case string(config2.IDERustRover): return jetbrains.NewRustRoverServer(config.GetRemoteUser(setupInfo), ide.Options, log).Install() case string(config2.IDEPyCharm): return jetbrains.NewPyCharmServer(config.GetRemoteUser(setupInfo), ide.Options, log).Install() case string(config2.IDEPhpStorm): return jetbrains.NewPhpStorm(config.GetRemoteUser(setupInfo), ide.Options, log).Install() case string(config2.IDEIntellij): return jetbrains.NewIntellij(config.GetRemoteUser(setupInfo), ide.Options, log).Install() case string(config2.IDECLion): return jetbrains.NewCLionServer(config.GetRemoteUser(setupInfo), ide.Options, log).Install() case string(config2.IDERider): return jetbrains.NewRiderServer(config.GetRemoteUser(setupInfo), ide.Options, log).Install() case string(config2.IDERubyMine): return jetbrains.NewRubyMineServer(config.GetRemoteUser(setupInfo), ide.Options, log).Install() case string(config2.IDEWebStorm): return jetbrains.NewWebStormServer(config.GetRemoteUser(setupInfo), ide.Options, log).Install() case string(config2.IDEDataSpell): return jetbrains.NewDataSpellServer(config.GetRemoteUser(setupInfo), ide.Options, log).Install() case string(config2.IDEFleet): return fleet.NewFleetServer(config.GetRemoteUser(setupInfo), ide.Options, log).Install(setupInfo.SubstitutionContext.ContainerWorkspaceFolder) case string(config2.IDEJupyterNotebook): return jupyter.NewJupyterNotebookServer(setupInfo.SubstitutionContext.ContainerWorkspaceFolder, config.GetRemoteUser(setupInfo), ide.Options, log).Install() case string(config2.IDERStudio): err := rstudio.NewRStudioServer(setupInfo.SubstitutionContext.ContainerWorkspaceFolder, config.GetRemoteUser(setupInfo), ide.Options, log).Install() if err != nil { log.Errorf("could not install rstudio with error: %w", err) } } return nil } func (cmd *SetupContainerCmd) setupVSCode(setupInfo *config.Result, ideOptions map[string]config2.OptionValue, flavor vscode.Flavor, log log.Logger) error { log.Debugf("Setup %s...", flavor.DisplayName()) vsCodeConfiguration := config.GetVSCodeConfiguration(setupInfo.MergedConfig) settings := "" if len(vsCodeConfiguration.Settings) > 0 { out, err := json.Marshal(vsCodeConfiguration.Settings) if err != nil { return err } settings = string(out) } user := config.GetRemoteUser(setupInfo) err := vscode.NewVSCodeServer(vsCodeConfiguration.Extensions, settings, user, ideOptions, flavor, log).Install() if err != nil { return err } // don't install code-server if we don't have settings or extensions if len(vsCodeConfiguration.Settings) == 0 && len(vsCodeConfiguration.Extensions) == 0 { return nil } if len(vsCodeConfiguration.Extensions) == 0 { return nil } return single.Single(fmt.Sprintf("%s-async.pid", flavor), func() (*exec.Cmd, error) { log.Infof("Install extensions '%s' in the background", strings.Join(vsCodeConfiguration.Extensions, ",")) binaryPath, err := os.Executable() if err != nil { return nil, err } args := []string{ "agent", "container", "vscode-async", "--setup-info", cmd.SetupInfo, "--release-channel", string(flavor), } return exec.Command(binaryPath, args...), nil }) } func (cmd *SetupContainerCmd) setupOpenVSCode(setupInfo *config.Result, ideOptions map[string]config2.OptionValue, log log.Logger) error { log.Debugf("Setup openvscode...") vsCodeConfiguration := config.GetVSCodeConfiguration(setupInfo.MergedConfig) settings := "" if len(vsCodeConfiguration.Settings) > 0 { out, err := json.Marshal(vsCodeConfiguration.Settings) if err != nil { return err } settings = string(out) } user := config.GetRemoteUser(setupInfo) openVSCode := openvscode.NewOpenVSCodeServer(vsCodeConfiguration.Extensions, settings, user, "0.0.0.0", strconv.Itoa(openvscode.DefaultVSCodePort), ideOptions, log) // install open vscode err := openVSCode.Install() if err != nil { return err } // install extensions in background if len(vsCodeConfiguration.Extensions) > 0 { err = single.Single("openvscode-async.pid", func() (*exec.Cmd, error) { log.Infof("Install extensions '%s' in the background", strings.Join(vsCodeConfiguration.Extensions, ",")) binaryPath, err := os.Executable() if err != nil { return nil, err } return exec.Command(binaryPath, "agent", "container", "openvscode-async", "--setup-info", cmd.SetupInfo), nil }) if err != nil { return errors.Wrap(err, "install extensions") } } // start the server in the background return openVSCode.Start() } func configureSystemGitCredentials(ctx context.Context, cancel context.CancelFunc, client tunnel.TunnelClient, log log.Logger) (func(), error) { if !command.Exists("git") { return nil, errors.New("git not found") } serverPort, err := credentials.StartCredentialsServer(ctx, cancel, client, log) if err != nil { return nil, err } binaryPath, err := os.Executable() if err != nil { return nil, err } gitCredentials := fmt.Sprintf("!'%s' agent git-credentials --port %d", binaryPath, serverPort) _ = os.Setenv("DEVPOD_GIT_HELPER_PORT", strconv.Itoa(serverPort)) err = git.CommandContext(ctx, git.GetDefaultExtraEnv(false), "config", "--system", "--add", "credential.helper", gitCredentials).Run() if err != nil { return nil, fmt.Errorf("add git credential helper: %w", err) } cleanup := func() { log.Debug("Unset setup system credential helper") err = git.CommandContext(ctx, git.GetDefaultExtraEnv(false), "config", "--system", "--unset", "credential.helper").Run() if err != nil { log.Errorf("unset system credential helper %v", err) } } return cleanup, nil } func streamMount(ctx context.Context, workspaceInfo *provider2.ContainerWorkspaceInfo, m *config.Mount, tunnelClient tunnel.TunnelClient, logger log.Logger) error { // if we have a platform workspace socket we connect directly to it if workspaceInfo.CLIOptions.Platform.Enabled { // check if the runner proxy socket exists httpClient := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, }, } // build the url logger.Infof("Download %s into DevContainer %s", m.Source, m.Target) url := fmt.Sprintf( "https://%s/kubernetes/management/apis/management.loft.sh/v1/namespaces/%s/devpodworkspaceinstances/%s/download?path=%s", ts.RemoveProtocol(workspaceInfo.CLIOptions.Platform.PlatformHost), workspaceInfo.CLIOptions.Platform.InstanceNamespace, workspaceInfo.CLIOptions.Platform.InstanceName, url.QueryEscape(m.Source), ) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return fmt.Errorf("create request: %w", err) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", workspaceInfo.CLIOptions.Platform.AccessKey)) // send the request resp, err := httpClient.Do(req) if err != nil { return fmt.Errorf("download workspace: %w", err) } defer resp.Body.Close() // check if the response is ok if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("download workspace: body = %s, status = %s", string(body), resp.Status) } // create progress reader progressReader := &progressReader{ Reader: resp.Body, Log: logger, } // target folder err = extract.Extract(progressReader, m.Target) if err != nil { return fmt.Errorf("stream mount %s: %w", m.String(), err) } return nil } // stream mount logger.Infof("Copy %s into DevContainer %s", m.Source, m.Target) stream, err := tunnelClient.StreamMount(ctx, &tunnel.StreamMountRequest{Mount: m.String()}) if err != nil { return fmt.Errorf("init stream mount %s: %w", m.String(), err) } // target folder err = extract.Extract(tunnelserver.NewStreamReader(stream, logger), m.Target) if err != nil { return fmt.Errorf("stream mount %s: %w", m.String(), err) } return nil } type progressReader struct { Reader io.Reader Log log.Logger lastMessage time.Time bytesRead int64 } func (p *progressReader) Read(b []byte) (n int, err error) { n, err = p.Reader.Read(b) p.bytesRead += int64(n) if time.Since(p.lastMessage) > time.Second*4 { p.Log.Infof("Downloaded %.2f MB", float64(p.bytesRead)/1024/1024) p.lastMessage = time.Now() } return n, err } ================================================ FILE: cmd/agent/container/setup_loft_platform_access.go ================================================ package container import ( "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/credentials" "github.com/loft-sh/devpod/pkg/loftconfig" "github.com/loft-sh/log" "github.com/spf13/cobra" ) type SetupLoftPlatformAccessCmd struct { *flags.GlobalFlags Context string Provider string Port int } // NewSetupLoftPlatformAccessCmd creates a new setup-loft-platform-access command // This agent command can be used to inject loft platform configuration from local machine to workspace. func NewSetupLoftPlatformAccessCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &SetupLoftPlatformAccessCmd{ GlobalFlags: flags, } setupLoftPlatformAccessCmd := &cobra.Command{ Use: "setup-loft-platform-access", Short: "used to setup Loft Platform access", RunE: cmd.Run, } setupLoftPlatformAccessCmd.Flags().StringVar(&cmd.Context, "context", "", "context to use") _ = setupLoftPlatformAccessCmd.Flags().MarkDeprecated("context", "Information should be provided by services server, don't use this flag anymore") setupLoftPlatformAccessCmd.Flags().StringVar(&cmd.Provider, "provider", "", "provider to use") _ = setupLoftPlatformAccessCmd.Flags().MarkDeprecated("provider", "Information should be provided by services server, don't use this flag anymore") setupLoftPlatformAccessCmd.Flags().IntVar(&cmd.Port, "port", 0, "If specified, will use the given port") _ = setupLoftPlatformAccessCmd.Flags().MarkDeprecated("port", "") return setupLoftPlatformAccessCmd } // Run executes main command logic. // It fetches Loft Platform credentials from credentials server and sets it up inside the workspace. func (c *SetupLoftPlatformAccessCmd) Run(_ *cobra.Command, args []string) error { logger := log.Default.ErrorStreamOnly() port, err := credentials.GetPort() if err != nil { return fmt.Errorf("get port: %w", err) } // backwards compatibility, remove in future release if c.Port > 0 { port = c.Port } loftConfig, err := loftconfig.GetLoftConfig(c.Context, c.Provider, port, logger) if err != nil { return err } if loftConfig == nil { logger.Debug("Got empty loft config response, Loft Platform access won't be set up.") return nil } err = loftconfig.AuthDevpodCliToPlatform(loftConfig, logger) if err != nil { // log error but don't return to allow other CLIs to install as well logger.Warnf("unable to authenticate devpod cli: %w", err) } err = loftconfig.AuthVClusterCliToPlatform(loftConfig, logger) if err != nil { // log error but don't return to allow other CLIs to install as well logger.Warnf("unable to authenticate vcluster cli: %w", err) } return nil } ================================================ FILE: cmd/agent/container/setup_windows.go ================================================ //go:build windows package container import ( "github.com/loft-sh/devpod/cmd/flags" "github.com/spf13/cobra" ) func NewSetupContainerCmd(flags *flags.GlobalFlags) *cobra.Command { return &cobra.Command{ Use: "setup", Short: "Sets up a container", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { panic("Windows Containers are not supported") }, } } ================================================ FILE: cmd/agent/container/ssh_server.go ================================================ package container import ( "fmt" "os" "path/filepath" "github.com/loft-sh/devpod/cmd/flags" helperssh "github.com/loft-sh/devpod/pkg/ssh/server" "github.com/loft-sh/devpod/pkg/ssh/server/port" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) const BaseLogDir = "/var/devpod" // SSHServerCmd holds the ssh server cmd flags type SSHServerCmd struct { *flags.GlobalFlags Address string Workdir string RemoteUser string } // NewSSHServerCmd creates a new ssh command func NewSSHServerCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &SSHServerCmd{ GlobalFlags: flags, } sshCmd := &cobra.Command{ Use: "ssh-server", Short: "Starts the container ssh server", Args: cobra.NoArgs, RunE: cmd.Run, } sshCmd.Flags().StringVar(&cmd.Address, "address", fmt.Sprintf("127.0.0.1:%d", helperssh.DefaultUserPort), "Address to listen to") sshCmd.Flags().StringVar(&cmd.RemoteUser, "remote-user", "", "The remote user for this workspace") sshCmd.Flags().StringVar(&cmd.Workdir, "workdir", "", "Directory where commands will run on the host") return sshCmd } // Run runs the command logic func (cmd *SSHServerCmd) Run(_ *cobra.Command, _ []string) error { logger := getFileLogger(cmd.RemoteUser, cmd.Debug) server, err := helperssh.NewContainerServer(cmd.Address, cmd.Workdir, logger) if err != nil { return err } // check if ssh is already running at that port available, err := port.IsAvailable(cmd.Address) if !available { if err != nil { return fmt.Errorf("address %s already in use: %w", cmd.Address, err) } log.Default.ErrorStreamOnly().Info("address %s already in use", cmd.Address) return nil } return server.ListenAndServe() } func getFileLogger(remoteUser string, debug bool) log.Logger { logLevel := logrus.InfoLevel if debug { logLevel = logrus.DebugLevel } fallback := log.NewDiscardLogger(logLevel) targetFolder := filepath.Join(os.TempDir(), ".devpod") if remoteUser != "" { targetFolder = filepath.Join(BaseLogDir, remoteUser) } err := os.MkdirAll(targetFolder, 0o755) if err != nil { return fallback } return log.NewFileLogger(filepath.Join(targetFolder, "ssh.log"), logLevel) } ================================================ FILE: cmd/agent/container/vscode_async.go ================================================ package container import ( "encoding/json" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/compress" "github.com/loft-sh/devpod/pkg/devcontainer/config" "github.com/loft-sh/devpod/pkg/ide/vscode" "github.com/loft-sh/log" "github.com/spf13/cobra" "github.com/spf13/pflag" ) // VSCodeAsyncCmd holds the cmd flags type VSCodeAsyncCmd struct { *flags.GlobalFlags SetupInfo string Flavor string } // NewVSCodeAsyncCmd creates a new command func NewVSCodeAsyncCmd() *cobra.Command { cmd := &VSCodeAsyncCmd{} vsCodeAsyncCmd := &cobra.Command{ Use: "vscode-async", Short: "Starts vscode", Args: cobra.NoArgs, RunE: cmd.Run, } vsCodeAsyncCmd.Flags().StringVar(&cmd.SetupInfo, "setup-info", "", "The container setup info") _ = vsCodeAsyncCmd.MarkFlagRequired("setup-info") vsCodeAsyncCmd.Flags().StringVar(&cmd.Flavor, "flavor", string(vscode.FlavorStable), "The flavor of the VSCode distribution") vsCodeAsyncCmd.Flags().StringVar(&cmd.Flavor, "release-channel", string(vscode.FlavorStable), "The release channel to use for vscode") _ = vsCodeAsyncCmd.Flags().MarkDeprecated("release-channel", "prefer the --flavor flag") // gracefully migrate --release-channel to --flavor vsCodeAsyncCmd.Flags().SetNormalizeFunc(migrateReleaseChannel) return vsCodeAsyncCmd } func migrateReleaseChannel(f *pflag.FlagSet, name string) pflag.NormalizedName { if name == "release-channel" { name = "flavor" } return pflag.NormalizedName(name) } // Run runs the command logic func (cmd *VSCodeAsyncCmd) Run(_ *cobra.Command, _ []string) error { log.Default.Debugf("Start setting up container...") decompressed, err := compress.Decompress(cmd.SetupInfo) if err != nil { return err } setupInfo := &config.Result{} err = json.Unmarshal([]byte(decompressed), setupInfo) if err != nil { return err } // install IDE err = setupVSCodeExtensions(setupInfo, vscode.Flavor(cmd.Flavor), log.Default) if err != nil { return err } return nil } func setupVSCodeExtensions(setupInfo *config.Result, flavor vscode.Flavor, log log.Logger) error { vsCodeConfiguration := config.GetVSCodeConfiguration(setupInfo.MergedConfig) user := config.GetRemoteUser(setupInfo) return vscode.NewVSCodeServer(vsCodeConfiguration.Extensions, "", user, nil, flavor, log).InstallExtensions() } ================================================ FILE: cmd/agent/container_tunnel.go ================================================ package agent import ( "bytes" "context" "io" "os" "os/signal" "syscall" "github.com/loft-sh/devpod/cmd/agent/workspace" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" "github.com/loft-sh/devpod/pkg/devcontainer" "github.com/loft-sh/devpod/pkg/devcontainer/config" "github.com/loft-sh/devpod/pkg/devcontainer/setup" "github.com/loft-sh/devpod/pkg/encoding" provider2 "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // ContainerTunnelCmd holds the ws-tunnel cmd flags type ContainerTunnelCmd struct { *flags.GlobalFlags WorkspaceInfo string User string } // NewContainerTunnelCmd creates a new command func NewContainerTunnelCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &ContainerTunnelCmd{ GlobalFlags: flags, } containerTunnelCmd := &cobra.Command{ Use: "container-tunnel", Short: "Starts a new container ssh tunnel", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { return cmd.Run(context.TODO(), log.Default.ErrorStreamOnly()) }, } containerTunnelCmd.Flags().StringVar(&cmd.User, "user", "", "The user to create the tunnel with") containerTunnelCmd.Flags().StringVar(&cmd.WorkspaceInfo, "workspace-info", "", "The workspace info") _ = containerTunnelCmd.MarkFlagRequired("workspace-info") return containerTunnelCmd } // Run runs the command logic func (cmd *ContainerTunnelCmd) Run(ctx context.Context, log log.Logger) error { // write workspace info shouldExit, workspaceInfo, err := agent.WriteWorkspaceInfo(cmd.WorkspaceInfo, log) if err != nil { return err } else if shouldExit { return nil } // make sure content folder exists _, err = workspace.InitContentFolder(workspaceInfo, log) if err != nil { return err } // create runner runner, err := workspace.CreateRunner(workspaceInfo, log) if err != nil { return err } // wait until devcontainer is started err = startDevContainer(ctx, workspaceInfo, runner, log) if err != nil { return err } // handle SIGHUP sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGHUP) go func() { <-sigs os.Exit(0) }() // create tunnel into container. err = agent.Tunnel( ctx, func(ctx context.Context, user string, command string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { return runner.Command(ctx, user, command, stdin, stdout, stderr) }, cmd.User, os.Stdin, os.Stdout, os.Stderr, log, workspaceInfo.InjectTimeout, ) if err != nil { return err } return nil } func startDevContainer(ctx context.Context, workspaceConfig *provider2.AgentWorkspaceInfo, runner devcontainer.Runner, log log.Logger) error { containerDetails, err := runner.Find(ctx) if err != nil { return err } // start container if necessary if containerDetails == nil || containerDetails.State.Status != "running" { // start container _, err = StartContainer(ctx, runner, log, workspaceConfig) if err != nil { return err } } else if encoding.IsLegacyUID(workspaceConfig.Workspace.UID) { // make sure workspace result is in devcontainer buf := &bytes.Buffer{} err = runner.Command(ctx, "root", "cat "+setup.ResultLocation, nil, buf, buf) if err != nil { // start container _, err = StartContainer(ctx, runner, log, workspaceConfig) if err != nil { return err } } } return nil } func StartContainer(ctx context.Context, runner devcontainer.Runner, log log.Logger, workspaceConfig *provider2.AgentWorkspaceInfo) (*config.Result, error) { log.Debugf("Starting DevPod container...") result, err := runner.Up(ctx, devcontainer.UpOptions{NoBuild: true}, workspaceConfig.InjectTimeout) if err != nil { return result, err } log.Debugf("Successfully started DevPod container") return result, err } ================================================ FILE: cmd/agent/daemon.go ================================================ package agent import ( "bytes" "context" "os" "path/filepath" "strings" "time" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/driver/custom" provider2 "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // DaemonCmd holds the cmd flags type DaemonCmd struct { *flags.GlobalFlags Interval string } // NewDaemonCmd creates a new command func NewDaemonCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &DaemonCmd{ GlobalFlags: flags, } daemonCmd := &cobra.Command{ Use: "daemon", Short: "Watches for activity and stops the server due to inactivity", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { return cmd.Run(context.Background()) }, } daemonCmd.Flags().StringVar(&cmd.Interval, "interval", "", "The interval how to poll workspaces") return daemonCmd } func (cmd *DaemonCmd) Run(ctx context.Context) error { logFolder, err := agent.GetAgentDaemonLogFolder(cmd.AgentDir) if err != nil { return err } logger := log.NewFileLogger(filepath.Join(logFolder, "agent-daemon.log"), logrus.InfoLevel) logger.Infof("Starting DevPod Daemon patrol at %s...", logFolder) // start patrolling cmd.patrol(logger) // should never reach this return nil } func (cmd *DaemonCmd) patrol(log log.Logger) { // make sure we don't immediately resleep on startup cmd.initialTouch(log) // parse the daemon interval interval := time.Second * 60 if cmd.Interval != "" { parsed, err := time.ParseDuration(cmd.Interval) if err == nil { interval = parsed } } // loop over workspace configs and check their last ModTime for { time.Sleep(interval) cmd.doOnce(log) } } func (cmd *DaemonCmd) doOnce(log log.Logger) { var latestActivity *time.Time var workspace *provider2.AgentWorkspaceInfo // get base folder baseFolder, err := agent.FindAgentHomeFolder(cmd.AgentDir) if err != nil { return } // get all workspace configs pattern := baseFolder + "/contexts/*/workspaces/*/" + provider2.WorkspaceConfigFile matches, err := filepath.Glob(pattern) if err != nil { log.Errorf("Error globing pattern %s: %v", pattern, err) return } // check when the last touch was for _, match := range matches { activity, activityWorkspace, err := getActivity(match, log) if err != nil { log.Errorf("Error checking for inactivity: %v", err) continue } else if activity == nil { continue } if latestActivity == nil || activity.After(*latestActivity) { latestActivity = activity workspace = activityWorkspace } } // should we run shutdown command? if latestActivity == nil { if len(matches) == 0 { log.Infof("No workspaces found in path '%s'", baseFolder) } else { log.Infof("%d workspaces found in path '%s', but none of them had any auto-stop configured or were still running / never completed successfully", len(matches), baseFolder) } return } // check timeout timeout := agent.DefaultInactivityTimeout if workspace.Agent.Timeout != "" { var err error timeout, err = time.ParseDuration(workspace.Agent.Timeout) if err != nil { log.Errorf("Error parsing inactivity timeout: %v", err) timeout = agent.DefaultInactivityTimeout } } if latestActivity.Add(timeout).After(time.Now()) { log.Infof("Workspace '%s' has latest activity at '%s', will auto-stop machine in %s", workspace.Workspace.ID, latestActivity.String(), time.Until(latestActivity.Add(timeout)).String()) return } // run shutdown command cmd.runShutdownCommand(workspace, log) } func (cmd *DaemonCmd) runShutdownCommand(workspace *provider2.AgentWorkspaceInfo, log log.Logger) { // get environ environ, err := custom.ToEnvironWithBinaries(workspace, log) if err != nil { log.Errorf("%v", err) return } // we run the timeout command now buf := &bytes.Buffer{} log.Infof("Run shutdown command for workspace %s: %s", workspace.Workspace.ID, strings.Join(workspace.Agent.Exec.Shutdown, " ")) err = clientimplementation.RunCommand( context.Background(), workspace.Agent.Exec.Shutdown, environ, nil, buf, buf, ) if err != nil { log.Errorf("Error running %s: %s%w", strings.Join(workspace.Agent.Exec.Shutdown, " "), buf.String(), err) return } log.Infof("Successful ran command: %s", buf.String()) } func (cmd *DaemonCmd) initialTouch(log log.Logger) { // get base folder baseFolder, err := agent.FindAgentHomeFolder(cmd.AgentDir) if err != nil { return } // get workspace configs pattern := baseFolder + "/contexts/*/workspaces/*/" + provider2.WorkspaceConfigFile matches, err := filepath.Glob(pattern) if err != nil { log.Errorf("Error globing pattern %s: %v", pattern, err) return } // check when the last touch was now := time.Now() for _, match := range matches { err := os.Chtimes(match, now, now) if err != nil { log.Errorf("Error touching workspace config %s: %v", pattern, err) return } } } func getActivity(workspaceConfig string, log log.Logger) (*time.Time, *provider2.AgentWorkspaceInfo, error) { workspace, err := agent.ParseAgentWorkspaceInfo(workspaceConfig) if err != nil { log.Errorf("Error reading %s: %v", workspaceConfig, err) return nil, nil, nil } // check if shutdown is configured if len(workspace.Agent.Exec.Shutdown) == 0 { return nil, nil, nil } // check last access time stat, err := os.Stat(workspaceConfig) if err != nil { return nil, nil, err } // check if workspace is locked t := stat.ModTime() if agent.HasWorkspaceBusyFile(filepath.Dir(workspaceConfig)) { t = t.Add(time.Minute * 20) } // check if timeout return &t, workspace, nil } ================================================ FILE: cmd/agent/docker_credentials.go ================================================ package agent import ( "bytes" "context" "encoding/json" "fmt" "io" "net" "net/http" "os" "path/filepath" "strconv" "strings" "time" "github.com/loft-sh/devpod/cmd/agent/container" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/dockercredentials" devpodhttp "github.com/loft-sh/devpod/pkg/http" "github.com/loft-sh/devpod/pkg/ts" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // DockerCredentialsCmd holds the cmd flags type DockerCredentialsCmd struct { *flags.GlobalFlags Port int } // NewDockerCredentialsCmd creates a new command func NewDockerCredentialsCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &DockerCredentialsCmd{ GlobalFlags: flags, } dockerCredentialsCmd := &cobra.Command{ Use: "docker-credentials", Short: "Retrieves docker-credentials from the local machine", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args, log.Default.ErrorStreamOnly()) }, } dockerCredentialsCmd.Flags().IntVar(&cmd.Port, "port", 0, "If specified, will use the given port") _ = dockerCredentialsCmd.MarkFlagRequired("port") return dockerCredentialsCmd } func (cmd *DockerCredentialsCmd) Run(ctx context.Context, args []string, log log.Logger) error { if len(args) == 0 { return nil } // we only handle get and list if args[0] == "get" { return cmd.handleGet(log) } else if args[0] == "list" { return cmd.handleList(log) } return nil } func (cmd *DockerCredentialsCmd) handleList(log log.Logger) error { rawJSON, err := json.Marshal(&dockercredentials.Request{}) if err != nil { return err } response, err := devpodhttp.GetHTTPClient().Post("http://localhost:"+strconv.Itoa(cmd.Port)+"/docker-credentials", "application/json", bytes.NewReader(rawJSON)) if err != nil { log.Errorf("Error retrieving list credentials: %v", err) return nil } defer response.Body.Close() raw, err := io.ReadAll(response.Body) if err != nil { log.Errorf("Error reading list credentials: %v", err) return nil } // has the request succeeded? if response.StatusCode != http.StatusOK { log.Errorf("Error reading list credentials (%d): %v", response.StatusCode, string(raw)) return nil } listResponse := &dockercredentials.ListResponse{} err = json.Unmarshal(raw, listResponse) if err != nil { log.Errorf("Error decoding list credentials: %s%v", string(raw), err) return nil } if listResponse.Registries == nil { listResponse.Registries = map[string]string{} } raw, err = json.Marshal(listResponse.Registries) if err != nil { log.Errorf("Error encoding list credentials: %v", err) return nil } // print response to stdout fmt.Print(string(raw)) return nil } func (cmd *DockerCredentialsCmd) handleGet(log log.Logger) error { url, err := io.ReadAll(os.Stdin) if err != nil { return err } else if len(strings.TrimSpace(string(url))) == 0 { return fmt.Errorf("no credentials server URL") } credentials := getDockerCredentialsFromWorkspaceServer(&dockercredentials.Credentials{ServerURL: strings.TrimSpace(string(url))}) if credentials != nil { raw, err := json.Marshal(credentials) if err != nil { log.Errorf("Error encoding credentials: %v", err) return nil } fmt.Print(string(raw)) return nil } rawJSON, err := json.Marshal(&dockercredentials.Request{ServerURL: strings.TrimSpace(string(url))}) if err != nil { return err } response, err := devpodhttp.GetHTTPClient().Post("http://localhost:"+strconv.Itoa(cmd.Port)+"/docker-credentials", "application/json", bytes.NewReader(rawJSON)) if err != nil { log.Errorf("Error retrieving credentials: %v", err) return nil } defer response.Body.Close() raw, err := io.ReadAll(response.Body) if err != nil { log.Errorf("Error reading credentials: %v", err) return nil } // has the request succeeded? if response.StatusCode != http.StatusOK { log.Errorf("Error reading credentials (%d): %v", response.StatusCode, string(raw)) return nil } // try to unmarshal err = json.Unmarshal(raw, &dockercredentials.Credentials{}) if err != nil { log.Errorf("Error parsing credentials: %v", err) return nil } // print response to stdout fmt.Print(string(raw)) return nil } func getDockerCredentialsFromWorkspaceServer(credentials *dockercredentials.Credentials) *dockercredentials.Credentials { if _, err := os.Stat(filepath.Join(container.RootDir, ts.RunnerProxySocket)); err != nil { // workspace server is not running return nil } httpClient := &http.Client{ Transport: &http.Transport{ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { return net.Dial("unix", filepath.Join(container.RootDir, ts.RunnerProxySocket)) }, }, Timeout: 15 * time.Second, } credentials, credentialsErr := requestDockerCredentials(httpClient, credentials, "http://runner-proxy/docker-credentials") if credentialsErr != nil { // append error to /var/devpod/docker-credentials.log file, err := os.OpenFile("/var/devpod/docker-credentials-error.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil } defer file.Close() _, _ = file.WriteString(fmt.Sprintf("get credentials from workspace server: %v\n", credentialsErr)) return nil } return credentials } func requestDockerCredentials(httpClient *http.Client, credentials *dockercredentials.Credentials, url string) (*dockercredentials.Credentials, error) { rawJSON, err := json.Marshal(credentials) if err != nil { return nil, fmt.Errorf("error marshalling credentials: %w", err) } response, err := httpClient.Post(url, "application/json", bytes.NewReader(rawJSON)) if err != nil { return nil, fmt.Errorf("error retrieving credentials from credentials server: %w", err) } defer response.Body.Close() raw, err := io.ReadAll(response.Body) if err != nil { return nil, fmt.Errorf("error reading credentials: %w", err) } // has the request succeeded? if response.StatusCode != http.StatusOK { return nil, fmt.Errorf("error reading credentials (%d): %s", response.StatusCode, string(raw)) } credentials = &dockercredentials.Credentials{} err = json.Unmarshal(raw, credentials) if err != nil { return nil, fmt.Errorf("error decoding credentials: %w", err) } return credentials, nil } ================================================ FILE: cmd/agent/git_credentials.go ================================================ package agent import ( "bytes" "context" "encoding/json" "fmt" "io" "net" "net/http" "os" "path/filepath" "strconv" "github.com/loft-sh/devpod/cmd/agent/container" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/gitcredentials" devpodhttp "github.com/loft-sh/devpod/pkg/http" "github.com/loft-sh/devpod/pkg/ts" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // GitCredentialsCmd holds the cmd flags type GitCredentialsCmd struct { *flags.GlobalFlags Port int } // NewGitCredentialsCmd creates a new command func NewGitCredentialsCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &GitCredentialsCmd{ GlobalFlags: flags, } gitCredentialsCmd := &cobra.Command{ Use: "git-credentials", Short: "Retrieves git-credentials from the local machine", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args, log.Default.ErrorStreamOnly()) }, } gitCredentialsCmd.Flags().IntVar(&cmd.Port, "port", 0, "If specified, will use the given port") return gitCredentialsCmd } func (cmd *GitCredentialsCmd) Run(ctx context.Context, args []string, log log.Logger) error { if len(args) == 0 { return nil } else if args[0] != "get" { return nil } raw, err := io.ReadAll(os.Stdin) if err != nil { return err } credentialsReq, err := gitcredentials.Parse(string(raw)) if err != nil { return err } // try to get the credentials from the workspace server first credentials := getCredentialsFromWorkspaceServer(credentialsReq) if credentials == nil && cmd.Port != 0 { // try to get the credentials from the local machine credentials = getCredentialsFromLocalMachine(credentialsReq, cmd.Port) } // if we still don't have credentials, just return nothing if credentials == nil { return nil } // print response to stdout fmt.Print(gitcredentials.ToString(credentials)) return nil } func getCredentialsFromWorkspaceServer(credentials *gitcredentials.GitCredentials) *gitcredentials.GitCredentials { if _, err := os.Stat(filepath.Join(container.RootDir, ts.RunnerProxySocket)); err != nil { // workspace server is not running return nil } httpClient := &http.Client{ Transport: &http.Transport{ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { return net.Dial("unix", filepath.Join(container.RootDir, ts.RunnerProxySocket)) }, }, } credentials, credentialsErr := doRequest(httpClient, credentials, "http://runner-proxy/git-credentials") if credentialsErr != nil { // append error to /tmp/git-credentials.log file, err := os.OpenFile("/tmp/git-credentials-error.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil } defer file.Close() _, _ = file.WriteString(fmt.Sprintf("get credentials from workspace server: %v\n", credentialsErr)) return nil } return credentials } func getCredentialsFromLocalMachine(credentials *gitcredentials.GitCredentials, port int) *gitcredentials.GitCredentials { credentials, credentialsErr := doRequest(devpodhttp.GetHTTPClient(), credentials, "http://localhost:"+strconv.Itoa(port)+"/git-credentials") if credentialsErr != nil { // append error to /tmp/git-credentials.log file, err := os.OpenFile("/tmp/git-credentials-error.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil } defer file.Close() _, _ = file.WriteString(fmt.Sprintf("get credentials from local machine: %v\n", credentialsErr)) return nil } return credentials } func doRequest(httpClient *http.Client, credentials *gitcredentials.GitCredentials, url string) (*gitcredentials.GitCredentials, error) { rawJSON, err := json.Marshal(credentials) if err != nil { return nil, fmt.Errorf("error marshalling credentials: %w", err) } response, err := httpClient.Post(url, "application/json", bytes.NewReader(rawJSON)) if err != nil { return nil, fmt.Errorf("error retrieving credentials from credentials server: %w", err) } defer response.Body.Close() raw, err := io.ReadAll(response.Body) if err != nil { return nil, fmt.Errorf("error reading credentials: %w", err) } // has the request succeeded? if response.StatusCode != http.StatusOK { return nil, fmt.Errorf("error reading credentials (%d): %s", response.StatusCode, string(raw)) } credentials = &gitcredentials.GitCredentials{} err = json.Unmarshal(raw, credentials) if err != nil { return nil, fmt.Errorf("error decoding credentials: %w", err) } return credentials, nil } ================================================ FILE: cmd/agent/git_ssh_signature.go ================================================ package agent import ( "errors" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/gitsshsigning" "github.com/loft-sh/log" "github.com/spf13/cobra" ) type GitSSHSignatureCmd struct { *flags.GlobalFlags CertPath string Namespace string BufferFile string Command string } // NewGitSSHSignatureCmd creates new git-ssh-signature command // This agent command can be used as git ssh program by setting // // > git config --global gpg.ssh.program "devpod agent git-ssh-signature" // // Git by default uses ssh-keygen for signing commits with ssh. This CLI command is a drop-in // replacement for ssh-keygen and hence needs to support ssh-keygen interface that git uses. // // custom-ssh-signature-handler -Y sign -n git -f /Users/johndoe/.ssh/my-key.pub /tmp/.git_signing_buffer_tmp4Euk6d func NewGitSSHSignatureCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &GitSSHSignatureCmd{ GlobalFlags: flags, } gitSshSignatureCmd := &cobra.Command{ Use: "git-ssh-signature", RunE: func(_ *cobra.Command, args []string) error { logger := log.GetInstance() if len(args) < 1 { logger.Fatalf("Buffer file is required") } // Check if the required -Y sign flags are present if cmd.Command != "sign" { return errors.New("must include '-Y sign' arguments") } // The last argument is the buffer file cmd.BufferFile = args[len(args)-1] return gitsshsigning.HandleGitSSHProgramCall( cmd.CertPath, cmd.Namespace, cmd.BufferFile, logger) }, } gitSshSignatureCmd.Flags().StringVarP(&cmd.CertPath, "file", "f", "", "Path to the private key") gitSshSignatureCmd.Flags().StringVarP(&cmd.Namespace, "namespace", "n", "", "Namespace") gitSshSignatureCmd.Flags().StringVarP(&cmd.Command, "command", "Y", "sign", "Command - should be 'sign'") return gitSshSignatureCmd } ================================================ FILE: cmd/agent/git_ssh_signature_helper.go ================================================ package agent import ( "fmt" "os/user" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/gitsshsigning" "github.com/loft-sh/log" "github.com/spf13/cobra" ) type GitSSHSignatureHelperCmd struct { *flags.GlobalFlags CertPath string } // NewGitSSHSignatureHelperCmd creates a new git-ssh-signature-helper command // This agent command can be used to inject the Git SSH signature helper. // // This command is used to set up the environment for Git SSH signature verification by configuring // the necessary helper using a provided signing key path. // // Example usage: // // git-ssh-signature-helper [signing-key-path] // // The signing key path is a required argument for this command. It should be what equal to what you would have set as user.signingkey git config. func NewGitSSHSignatureHelperCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &GitSSHSignatureCmd{ GlobalFlags: flags, } gitSshSignatureHelperCmd := &cobra.Command{ Use: "git-ssh-signature-helper [signing-key-path]", Short: "used to inject git ssh signature helper", RunE: func(_ *cobra.Command, args []string) error { usr, err := user.Current() if err != nil { return err } if len(args) < 1 { return fmt.Errorf("gitSigningKey argument is required") } cmd.CertPath = args[0] log := log.GetInstance() err = gitsshsigning.ConfigureHelper(usr.Username, cmd.CertPath, log) if err != nil { return err } return nil }, } return gitSshSignatureHelperCmd } ================================================ FILE: cmd/agent/workspace/build.go ================================================ package workspace import ( "context" "os" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" provider2 "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" ) // BuildCmd holds the cmd flags type BuildCmd struct { *flags.GlobalFlags WorkspaceInfo string } // NewBuildCmd creates a new command func NewBuildCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &BuildCmd{ GlobalFlags: flags, } buildCmd := &cobra.Command{ Use: "build", Short: "Builds a devcontainer", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { return cmd.Run(context.Background()) }, } buildCmd.Flags().StringVar(&cmd.WorkspaceInfo, "workspace-info", "", "The workspace info") _ = buildCmd.MarkFlagRequired("workspace-info") return buildCmd } // Run runs the command logic func (cmd *BuildCmd) Run(ctx context.Context) error { // write workspace info shouldExit, workspaceInfo, err := agent.WriteWorkspaceInfoAndDeleteOld(cmd.WorkspaceInfo, func(workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) error { return deleteWorkspace(ctx, workspaceInfo, log) }, log.Default.ErrorStreamOnly()) if err != nil { return err } else if shouldExit { return nil } // make sure daemon does shut us down while we are doing things agent.CreateWorkspaceBusyFile(workspaceInfo.Origin) defer agent.DeleteWorkspaceBusyFile(workspaceInfo.Origin) // initialize the workspace cancelCtx, cancel := context.WithCancel(ctx) defer cancel() _, logger, credentialsDir, err := initWorkspace(cancelCtx, cancel, workspaceInfo, cmd.Debug, false) if err != nil { return err } else if credentialsDir != "" { defer func() { _ = os.RemoveAll(credentialsDir) }() } runner, err := CreateRunner(workspaceInfo, logger) if err != nil { return err } // if there is no platform specified, we use empty to let // the builder find out itself. platforms := workspaceInfo.CLIOptions.Platforms if len(platforms) == 0 { platforms = []string{""} } // build and push images for _, platform := range platforms { // build the image imageName, err := runner.Build(ctx, provider2.BuildOptions{ CLIOptions: workspaceInfo.CLIOptions, RegistryCache: workspaceInfo.RegistryCache, Platform: platform, ExportCache: true, }) if err != nil { logger.Errorf("Error building image: %v", err) return errors.Wrap(err, "build") } if workspaceInfo.CLIOptions.SkipPush { logger.Donef("Successfully build image %s", imageName) } else { logger.Donef("Successfully build and pushed image %s", imageName) } } return nil } func deleteWorkspace(ctx context.Context, workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) error { err := removeContainer(ctx, workspaceInfo, log) if err != nil { log.Errorf("Removing container: %v", err) } _ = os.RemoveAll(workspaceInfo.Origin) return nil } ================================================ FILE: cmd/agent/workspace/delete.go ================================================ package workspace import ( "context" "fmt" "os" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" agentdaemon "github.com/loft-sh/devpod/pkg/daemon/agent" provider2 "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" ) // DeleteCmd holds the cmd flags type DeleteCmd struct { *flags.GlobalFlags Container bool Daemon bool WorkspaceInfo string } // NewDeleteCmd creates a new command func NewDeleteCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &DeleteCmd{ GlobalFlags: flags, } deleteCmd := &cobra.Command{ Use: "delete", Short: "Cleans up a workspace on the remote server", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { return cmd.Run(context.Background()) }, } deleteCmd.Flags().BoolVar(&cmd.Container, "container", true, "If enabled, cleans up the DevPod container") deleteCmd.Flags().BoolVar(&cmd.Daemon, "daemon", false, "If enabled, cleans up the DevPod daemon") deleteCmd.Flags().StringVar(&cmd.WorkspaceInfo, "workspace-info", "", "The workspace info") _ = deleteCmd.MarkFlagRequired("workspace-info") return deleteCmd } func (cmd *DeleteCmd) Run(ctx context.Context) error { // get workspace shouldExit, workspaceInfo, err := agent.WorkspaceInfo(cmd.WorkspaceInfo, log.Default.ErrorStreamOnly()) if err != nil { return fmt.Errorf("error parsing workspace info: %w", err) } else if shouldExit { return nil } // remove daemon if cmd.Daemon { err = removeDaemon(workspaceInfo, log.Default) if err != nil { return errors.Wrap(err, "remove daemon") } } // cleanup docker container if cmd.Container { err = removeContainer(ctx, workspaceInfo, log.Default) if err != nil { return errors.Wrap(err, "remove container") } } // delete workspace folder _ = os.RemoveAll(workspaceInfo.Origin) return nil } func removeContainer(ctx context.Context, workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) error { log.Debugf("Removing DevPod container from server...") runner, err := CreateRunner(workspaceInfo, log) if err != nil { return err } if workspaceInfo.Workspace.Source.Container != "" { log.Infof("Skipping container deletion, since it was not created by DevPod") } else { err = runner.Delete(ctx) if err != nil { return err } log.Debugf("Successfully removed DevPod container from server") } return nil } func removeDaemon(workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) error { if len(workspaceInfo.Agent.Exec.Shutdown) == 0 { return nil } log.Debugf("Removing DevPod daemon from server...") err := agentdaemon.RemoveDaemon() if err != nil { return errors.Wrap(err, "remove daemon") } log.Debugf("Successfully removed DevPod daemon from server") return nil } ================================================ FILE: cmd/agent/workspace/install_dotfiles.go ================================================ package workspace import ( "context" "os" "os/exec" "path/filepath" "strings" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/git" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // InstallDotfilesCmd holds the installDotfiles cmd flags type InstallDotfilesCmd struct { *flags.GlobalFlags Repository string InstallScript string StrictHostKeyChecking bool } // NewInstallDotfilesCmd creates a new command func NewInstallDotfilesCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &InstallDotfilesCmd{ GlobalFlags: flags, } installDotfilesCmd := &cobra.Command{ Use: "install-dotfiles", Short: "installs input dotfiles in the container", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { return cmd.Run(context.Background()) }, } installDotfilesCmd.Flags().StringVar(&cmd.Repository, "repository", "", "The dotfiles repository") installDotfilesCmd.Flags().StringVar(&cmd.InstallScript, "install-script", "", "The dotfiles install command to execute") installDotfilesCmd.Flags().BoolVar(&cmd.StrictHostKeyChecking, "strict-host-key-checking", false, "Set to enable strict host key checking for git cloning via SSH") return installDotfilesCmd } // Run runs the command logic func (cmd *InstallDotfilesCmd) Run(ctx context.Context) error { logger := log.Default.ErrorStreamOnly() targetDir := filepath.Join(os.Getenv("HOME"), "dotfiles") _, err := os.Stat(targetDir) if err != nil { logger.Infof("Cloning dotfiles %s", cmd.Repository) gitInfo := git.NormalizeRepositoryGitInfo(cmd.Repository) if err := git.CloneRepository(ctx, gitInfo, targetDir, "", cmd.StrictHostKeyChecking, logger); err != nil { return err } } else { logger.Info("dotfiles already set up, skipping cloning") } logger.Debugf("Entering dotfiles directory") err = os.Chdir(targetDir) if err != nil { return err } if cmd.InstallScript != "" { logger.Infof("Executing install script %s", cmd.InstallScript) command := "./" + strings.TrimPrefix(cmd.InstallScript, "./") err := ensureExecutable(command) if err != nil { return errors.Wrapf(err, "failed to make install script %s executable", command) } scriptCmd := exec.Command(command) writer := logger.Writer(logrus.InfoLevel, false) scriptCmd.Stdout = writer scriptCmd.Stderr = writer return scriptCmd.Run() } logger.Debugf("Install script not specified, trying known locations") return setupDotfiles(logger) } var scriptLocations = []string{ "./install.sh", "./install", "./bootstrap.sh", "./bootstrap", "./script/bootstrap", "./setup.sh", "./setup", "./setup/setup", } func setupDotfiles(logger log.Logger) error { for _, command := range scriptLocations { logger.Debugf("Trying executing %s", command) writer := logger.Writer(logrus.InfoLevel, false) err := ensureExecutable(command) if err != nil { logger.Infof("Failed to make install script %s executable: %v", command, err) logger.Debug("Trying next location") continue } scriptCmd := exec.Command(command) scriptCmd.Stdout = writer scriptCmd.Stderr = writer err = scriptCmd.Run() if err != nil { logger.Infof("Execution of %s was unsuccessful: %v", command, err) logger.Debug("Trying next location") continue } // we successfully executed one of the commands, let's exit return nil } logger.Info("Finished script locations, trying to link the files") files, err := os.ReadDir(".") if err != nil { return err } pwd, err := os.Getwd() if err != nil { return err } // link dotfiles in directory to home for _, file := range files { if strings.HasPrefix(file.Name(), ".") && !file.IsDir() { logger.Debugf("linking %s in home", file.Name()) // remove existing symlink and relink if _, err := os.Lstat(filepath.Join(os.Getenv("HOME"), file.Name())); err == nil { os.Remove(filepath.Join(os.Getenv("HOME"), file.Name())) } err = os.Symlink(filepath.Join(pwd, file.Name()), filepath.Join(os.Getenv("HOME"), file.Name())) if err != nil { return err } } } return nil } func ensureExecutable(path string) error { checkCmd := exec.Command("test", "-f", path) err := checkCmd.Run() if err != nil { return errors.Wrapf(err, "install script %s not found", path) } chmodCmd := exec.Command("chmod", "+x", path) err = chmodCmd.Run() if err != nil { return errors.Wrapf(err, "failed to make install script %s executable", path) } return nil } ================================================ FILE: cmd/agent/workspace/logs.go ================================================ package workspace import ( "context" "fmt" "os" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" "github.com/loft-sh/devpod/pkg/devcontainer" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // LogsCmd holds the cmd flags type LogsCmd struct { *flags.GlobalFlags ID string } // NewLogsCmd creates a new command func NewLogsCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &LogsCmd{ GlobalFlags: flags, } c := &cobra.Command{ Use: "logs", Short: "Returns the workspace container logs", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { return cmd.Run(context.Background()) }, } c.Flags().StringVar(&cmd.ID, "id", "", "The workspace id") _ = c.MarkFlagRequired("id") return c } func (cmd *LogsCmd) Run(ctx context.Context) error { // get workspace info shouldExit, workspaceInfo, err := agent.ReadAgentWorkspaceInfo(cmd.AgentDir, cmd.Context, cmd.ID, log.Default.ErrorStreamOnly()) if err != nil { return err } else if shouldExit { return nil } logger := log.Default.ErrorStreamOnly() // create new runner runner, err := devcontainer.NewRunner(agent.ContainerDevPodHelperLocation, agent.DefaultAgentDownloadURL(), workspaceInfo, logger) if err != nil { return fmt.Errorf("create runner: %w", err) } // write devcontainer logs to stdout return runner.Logs(ctx, os.Stdout) } ================================================ FILE: cmd/agent/workspace/logs_daemon.go ================================================ package workspace import ( "context" "io" "os" "path/filepath" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" ) // LogsDaemonCmd holds the cmd flags type LogsDaemonCmd struct { *flags.GlobalFlags ID string } // NewLogsDaemonCmd creates a new command func NewLogsDaemonCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &LogsDaemonCmd{ GlobalFlags: flags, } logsDaemonCmd := &cobra.Command{ Use: "logs-daemon", Short: "Returns the daemon logs", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { return cmd.Run(context.Background()) }, } logsDaemonCmd.Flags().StringVar(&cmd.ID, "id", "", "The workspace id") _ = logsDaemonCmd.MarkFlagRequired("id") return logsDaemonCmd } func (cmd *LogsDaemonCmd) Run(ctx context.Context) error { // get workspace shouldExit, _, err := agent.ReadAgentWorkspaceInfo(cmd.AgentDir, cmd.Context, cmd.ID, log.Default.ErrorStreamOnly()) if err != nil { return err } else if shouldExit { return nil } logFolder, err := agent.GetAgentDaemonLogFolder(cmd.AgentDir) if err != nil { return err } f, err := os.Open(filepath.Join(logFolder, "agent-daemon.log")) if err != nil { return errors.Wrap(err, "open agent-daemon.log") } defer f.Close() _, err = io.Copy(os.Stdout, f) return err } ================================================ FILE: cmd/agent/workspace/setup_gpg.go ================================================ package workspace import ( "context" "encoding/base64" "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/credentials" "github.com/loft-sh/devpod/pkg/gitcredentials" "github.com/loft-sh/devpod/pkg/gpg" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // SetupGPGCmd holds the setupGPG cmd flags type SetupGPGCmd struct { *flags.GlobalFlags OwnerTrust string SocketPath string GitKey string } // NewSetupGPGCmd creates a new command func NewSetupGPGCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &SetupGPGCmd{ GlobalFlags: flags, } setupGPGCmd := &cobra.Command{ Use: "setup-gpg", Short: "setups gpg-agent forwarding in the container", Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, _ []string) error { return cmd.Run(cobraCmd.Context(), log.Default.ErrorStreamOnly()) }, } setupGPGCmd.Flags().StringVar(&cmd.OwnerTrust, "ownertrust", "", "GPG Owner trust to import in armor form") setupGPGCmd.Flags().StringVar(&cmd.SocketPath, "socketpath", "", "path to the gpg socket forwarded") setupGPGCmd.Flags().StringVar(&cmd.GitKey, "gitkey", "", "gpg key to use for git commit signing") return setupGPGCmd } // will forward a local gpg-agent into the remote container // this works by // // - stopping remote gpg-agent and removing the sockets // - exporting local public keys and owner trust // - importing those into the container // - ensuring the gpg-agent is stopped in the container // - starting a reverse-tunnel of the local unix socket to remote // - ensuring paths and permissions are correctly set in the remote func (cmd *SetupGPGCmd) Run(ctx context.Context, log log.Logger) error { log.Debugf("Initializing gpg-agent forwarding") log.Debugf("Fetching public key") rawPublicKeys, err := getPublicKeys(log) if err != nil { log.Errorf("Fetch public key: %v", err) return err } log.Debugf("Decoding public key") publicKey, err := base64.StdEncoding.DecodeString(rawPublicKeys) if err != nil { return err } log.Debugf("Decoding input owner trust") ownerTrust, err := base64.StdEncoding.DecodeString(cmd.OwnerTrust) if err != nil { return err } gpgConf := gpg.GPGConf{ PublicKey: publicKey, OwnerTrust: ownerTrust, SocketPath: cmd.SocketPath, GitKey: cmd.GitKey, } log.Debugf("Stopping container gpg-agent") err = gpgConf.StopGpgAgent() if err != nil { log.Errorf("stop container gpg-agent: %v", err) return err } log.Debugf("Importing gpg public key in container") err = gpgConf.ImportGpgKey() if err != nil { log.Errorf("Import gpg public key in container: %v", err) return err } log.Debugf("Importing gpg owner trust in container") err = gpgConf.ImportOwnerTrust() if err != nil { log.Errorf("Import gpg owner trust in container: %v", err) return err } log.Debugf("Ensuring paths existence and permissions") err = gpgConf.SetupRemoteSocketDirTree() if err != nil { log.Errorf("Ensure paths existence and permissions: %v", err) return err } // Now we again kill the agent and remove the socket to really be sure every // thing is clean log.Debugf("Ensure stopping container gpg-agent") err = gpgConf.StopGpgAgent() if err != nil { log.Errorf("Ensure stopping container gpg-agent: %v", err) return err } log.Debugf("Setup local gnupg socket links") err = gpgConf.SetupRemoteSocketLink() if err != nil { log.Errorf("Setup local gnupg socket links: %v", err) return err } log.Debugf("Setup gpg.conf") err = gpgConf.SetupGpgConf() if err != nil { log.Errorf("Setup gpg.conf: %v", err) return err } if gpgConf.GitKey != "" { log.Debugf("Setup git signing key") err = gitcredentials.SetupGpgGitKey(gpgConf.GitKey) if err != nil { log.Errorf("Setup git signing key: %v", err) return err } } return nil } func getPublicKeys(log log.Logger) (string, error) { port, err := credentials.GetPort() if err != nil { return "", fmt.Errorf("get port: %w", err) } out, err := credentials.PostWithRetry(port, "gpg-public-keys", nil, log) if err != nil { return "", fmt.Errorf("get public gpg keys: %w", err) } return string(out), nil } ================================================ FILE: cmd/agent/workspace/status.go ================================================ package workspace import ( "context" "fmt" "strings" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // StatusCmd holds the cmd flags type StatusCmd struct { *flags.GlobalFlags WorkspaceInfo string } // NewStatusCmd creates a new command func NewStatusCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &StatusCmd{ GlobalFlags: flags, } statusCmd := &cobra.Command{ Use: "status", Short: "Print the status of a remote container", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { return cmd.Run(context.Background(), log.Default.ErrorStreamOnly()) }, } statusCmd.Flags().StringVar(&cmd.WorkspaceInfo, "workspace-info", "", "The workspace info") _ = statusCmd.MarkFlagRequired("workspace-info") return statusCmd } func (cmd *StatusCmd) Run(ctx context.Context, log log.Logger) error { // get workspace shouldExit, workspaceInfo, err := agent.WorkspaceInfo(cmd.WorkspaceInfo, log) if err != nil { return err } else if shouldExit { return nil } // create runner runner, err := CreateRunner(workspaceInfo, log) if err != nil { return err } // find dev container containerDetails, err := runner.Find(ctx) if err != nil { return err } else if containerDetails == nil { fmt.Print(client.StatusNotFound) return nil } // is running? if strings.ToLower(containerDetails.State.Status) == "running" { fmt.Print(client.StatusRunning) return nil } else if strings.ToLower(containerDetails.State.Status) == "exited" { fmt.Print(client.StatusStopped) return nil } fmt.Print(client.StatusBusy) return nil } ================================================ FILE: cmd/agent/workspace/stop.go ================================================ package workspace import ( "context" "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" provider2 "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" ) // StopCmd holds the cmd flags type StopCmd struct { *flags.GlobalFlags WorkspaceInfo string } // NewStopCmd creates a new command func NewStopCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &StopCmd{ GlobalFlags: flags, } stopCmd := &cobra.Command{ Use: "stop", Short: "Stops a workspace on the remote server", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { return cmd.Run(context.Background()) }, } stopCmd.Flags().StringVar(&cmd.WorkspaceInfo, "workspace-info", "", "The workspace info") _ = stopCmd.MarkFlagRequired("workspace-info") return stopCmd } func (cmd *StopCmd) Run(ctx context.Context) error { // get workspace shouldExit, workspaceInfo, err := agent.WriteWorkspaceInfo(cmd.WorkspaceInfo, log.Default.ErrorStreamOnly()) if err != nil { return fmt.Errorf("error parsing workspace info: %w", err) } else if shouldExit { return nil } // stop docker container err = stopContainer(ctx, workspaceInfo, log.Default) if err != nil { return errors.Wrap(err, "stop container") } return nil } func stopContainer(ctx context.Context, workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) error { log.Debugf("Stopping DevPod container...") runner, err := CreateRunner(workspaceInfo, log) if err != nil { return err } err = runner.Stop(ctx) if err != nil { return err } log.Debugf("Successfully stopped DevPod container") return nil } ================================================ FILE: cmd/agent/workspace/up.go ================================================ package workspace import ( "context" "encoding/json" "fmt" "os" "os/exec" "path/filepath" "strconv" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" "github.com/loft-sh/devpod/pkg/agent/tunnel" "github.com/loft-sh/devpod/pkg/agent/tunnelserver" "github.com/loft-sh/devpod/pkg/binaries" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/command" "github.com/loft-sh/devpod/pkg/credentials" agentdaemon "github.com/loft-sh/devpod/pkg/daemon/agent" "github.com/loft-sh/devpod/pkg/devcontainer" config2 "github.com/loft-sh/devpod/pkg/devcontainer/config" "github.com/loft-sh/devpod/pkg/devcontainer/crane" "github.com/loft-sh/devpod/pkg/dockercredentials" "github.com/loft-sh/devpod/pkg/extract" provider2 "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/util" "github.com/loft-sh/devpod/scripts" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // UpCmd holds the up cmd flags type UpCmd struct { *flags.GlobalFlags WorkspaceInfo string } // NewUpCmd creates a new command func NewUpCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &UpCmd{ GlobalFlags: flags, } upCmd := &cobra.Command{ Use: "up", Short: "Starts a new devcontainer", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { return cmd.Run(context.Background()) }, } upCmd.Flags().StringVar(&cmd.WorkspaceInfo, "workspace-info", "", "The workspace info") _ = upCmd.MarkFlagRequired("workspace-info") return upCmd } // Run runs the command logic func (cmd *UpCmd) Run(ctx context.Context) error { // get workspace shouldExit, workspaceInfo, err := agent.WriteWorkspaceInfoAndDeleteOld(cmd.WorkspaceInfo, func(workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) error { return deleteWorkspace(ctx, workspaceInfo, log) }, log.Default.ErrorStreamOnly()) if err != nil { return fmt.Errorf("error parsing workspace info: %w", err) } else if shouldExit { return nil } // make sure daemon doesn't shut us down while we are doing things if !workspaceInfo.CLIOptions.Platform.Enabled { agent.CreateWorkspaceBusyFile(workspaceInfo.Origin) defer agent.DeleteWorkspaceBusyFile(workspaceInfo.Origin) } // initialize the workspace cancelCtx, cancel := context.WithCancel(ctx) defer cancel() tunnelClient, logger, credentialsDir, err := initWorkspace(cancelCtx, cancel, workspaceInfo, cmd.Debug, !workspaceInfo.CLIOptions.Platform.Enabled && !workspaceInfo.CLIOptions.DisableDaemon) if err != nil { err1 := clientimplementation.DeleteWorkspaceFolder(workspaceInfo.Workspace.Context, workspaceInfo.Workspace.ID, workspaceInfo.Workspace.SSHConfigPath, logger) if err1 != nil { return errors.Wrap(err, err1.Error()) } return err } else if credentialsDir != "" { defer func() { _ = os.RemoveAll(credentialsDir) }() } // start up err = cmd.up(ctx, workspaceInfo, tunnelClient, logger) if err != nil { return errors.Wrap(err, "devcontainer up") } return nil } func (cmd *UpCmd) up(ctx context.Context, workspaceInfo *provider2.AgentWorkspaceInfo, tunnelClient tunnel.TunnelClient, logger log.Logger) error { // create devcontainer result, err := cmd.devPodUp(ctx, workspaceInfo, logger) if err != nil { return err } // send result out, err := json.Marshal(result) if err != nil { return err } _, err = tunnelClient.SendResult(ctx, &tunnel.Message{Message: string(out)}) if err != nil { return errors.Wrap(err, "send result") } return nil } func (cmd *UpCmd) devPodUp(ctx context.Context, workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) (*config2.Result, error) { runner, err := CreateRunner(workspaceInfo, log) if err != nil { return nil, err } // start the devcontainer result, err := runner.Up(ctx, devcontainer.UpOptions{ CLIOptions: workspaceInfo.CLIOptions, RegistryCache: workspaceInfo.RegistryCache, }, workspaceInfo.InjectTimeout) if err != nil { return nil, err } return result, nil } func CreateRunner(workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) (devcontainer.Runner, error) { return devcontainer.NewRunner(agent.ContainerDevPodHelperLocation, agent.DefaultAgentDownloadURL(), workspaceInfo, log) } func InitContentFolder(workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) (bool, error) { // check if workspace content folder exists _, err := os.Stat(workspaceInfo.ContentFolder) if err == nil { log.Debugf("Workspace Folder already exists %s", workspaceInfo.ContentFolder) return true, nil } // make content dir log.Debugf("Create content folder %s", workspaceInfo.ContentFolder) err = os.MkdirAll(workspaceInfo.ContentFolder, 0o777) if err != nil { return false, errors.Wrap(err, "make workspace folder") } // download provider binariesDir, err := agent.GetAgentBinariesDir(workspaceInfo.Agent.DataPath, workspaceInfo.Workspace.Context, workspaceInfo.Workspace.ID) if err != nil { _ = os.RemoveAll(workspaceInfo.ContentFolder) return false, fmt.Errorf("error getting workspace %s binaries dir: %w", workspaceInfo.Workspace.ID, err) } // download binaries _, err = binaries.DownloadBinaries(workspaceInfo.Agent.Binaries, binariesDir, log) if err != nil { _ = os.RemoveAll(workspaceInfo.ContentFolder) return false, fmt.Errorf("error downloading workspace %s binaries: %w", workspaceInfo.Workspace.ID, err) } // if workspace was already executed, we skip this part if workspaceInfo.LastDevContainerConfig != nil { // make sure the devcontainer.json exists err = ensureLastDevContainerJson(workspaceInfo) if err != nil { log.Errorf("Ensure devcontainer.json: %v", err) } return true, nil } return false, nil } func initWorkspace(ctx context.Context, cancel context.CancelFunc, workspaceInfo *provider2.AgentWorkspaceInfo, debug, shouldInstallDaemon bool) (tunnel.TunnelClient, log.Logger, string, error) { // create a grpc client tunnelClient, err := tunnelserver.NewTunnelClient(os.Stdin, os.Stdout, true, 0) if err != nil { return nil, nil, "", fmt.Errorf("error creating tunnel client: %w", err) } // create debug logger logger := tunnelserver.NewTunnelLogger(ctx, tunnelClient, debug) logger.Debugf("Created logger") // this message serves as a ping to the client _, err = tunnelClient.Ping(ctx, &tunnel.Empty{}) if err != nil { return nil, nil, "", errors.Wrap(err, "ping client") } // get docker credentials dockerCredentialsDir, gitCredentialsHelper, err := configureCredentials(ctx, cancel, workspaceInfo, tunnelClient, logger) if err != nil { logger.Errorf("Error retrieving docker / git credentials: %v", err) } // install docker in background errChan := make(chan error, 2) go func() { if !workspaceInfo.Agent.IsDockerDriver() || workspaceInfo.Agent.Docker.Install == "false" { errChan <- nil } else { errChan <- installDocker(logger) } }() // prepare workspace err = prepareWorkspace(ctx, workspaceInfo, tunnelClient, gitCredentialsHelper, logger) if err != nil { return nil, logger, "", err } // install daemon if shouldInstallDaemon { err = installDaemon(workspaceInfo, logger) if err != nil { logger.Errorf("Install DevPod Daemon: %v", err) } } // wait until docker is installed before configuring docker daemon err = <-errChan if err != nil { return nil, nil, "", errors.Wrap(err, "install docker") } // If we are provisioning the machine, ensure the daemon has required options local, err := workspaceInfo.Agent.Local.Bool() if workspaceInfo.Agent.IsDockerDriver() && err != nil && !local { errChan <- configureDockerDaemon(ctx, logger) } else { logger.Debug("Skipping configuring docker daemon") errChan <- nil } // wait until docker daemon is configured err = <-errChan if err != nil { logger.Warn("Could not find docker daemon config file, if using the registry cache, please ensure the daemon is configured with containerd-snapshotter=true") logger.Warn("More info at https://docs.docker.com/engine/storage/containerd/") } return tunnelClient, logger, dockerCredentialsDir, nil } func prepareWorkspace(ctx context.Context, workspaceInfo *provider2.AgentWorkspaceInfo, client tunnel.TunnelClient, helper string, log log.Logger) error { // change content folder if source is local folder in proxy mode // to a folder that's known ahead of time inside of DEVPOD_HOME if workspaceInfo.CLIOptions.Platform.Enabled && workspaceInfo.Workspace.Source.LocalFolder != "" { workspaceInfo.ContentFolder = agent.GetAgentWorkspaceContentDir(workspaceInfo.Origin) } // make sure content folder exists exists, err := InitContentFolder(workspaceInfo, log) if err != nil { return err } else if exists && !workspaceInfo.CLIOptions.Recreate { log.Debugf("Workspace exists, skip downloading") return nil } // check what type of workspace this is if workspaceInfo.Workspace.Source.GitRepository != "" { if workspaceInfo.CLIOptions.Reset { log.Info("Resetting git based workspace, removing old content folder") err = os.RemoveAll(workspaceInfo.ContentFolder) if err != nil { log.Warnf("Failed to remove workspace folder, still proceeding: %v", err) } } if workspaceInfo.CLIOptions.Recreate && !workspaceInfo.CLIOptions.Reset && exists { log.Info("Rebuilding without resetting a git based workspace, keeping old content folder") return nil } if crane.ShouldUse(&workspaceInfo.CLIOptions) { log.Infof("Pulling devcontainer spec from %v", workspaceInfo.CLIOptions.Platform.EnvironmentTemplate) return nil } return agent.CloneRepositoryForWorkspace(ctx, &workspaceInfo.Workspace.Source, &workspaceInfo.Agent, workspaceInfo.ContentFolder, helper, workspaceInfo.CLIOptions, false, log, ) } if workspaceInfo.Workspace.Source.LocalFolder != "" { // if we're not sending this to a remote machine, we're already done if workspaceInfo.ContentFolder == workspaceInfo.Workspace.Source.LocalFolder { log.Debugf("Local folder %s with local provider; skip downloading", workspaceInfo.ContentFolder) return nil } log.Debugf("Download Local Folder %s", workspaceInfo.ContentFolder) return downloadLocalFolder(ctx, workspaceInfo.ContentFolder, client, log) } if workspaceInfo.Workspace.Source.Image != "" { log.Debugf("Prepare Image") return prepareImage(workspaceInfo.ContentFolder, workspaceInfo.Workspace.Source.Image) } if workspaceInfo.Workspace.Source.Container != "" { log.Debugf("Workspace is a container, nothing to do") return nil } return fmt.Errorf("either workspace repository, image, container or local-folder is required") } func ensureLastDevContainerJson(workspaceInfo *provider2.AgentWorkspaceInfo) error { filePath := filepath.Join(workspaceInfo.ContentFolder, filepath.FromSlash(workspaceInfo.LastDevContainerConfig.Path)) _, err := os.Stat(filePath) if os.IsNotExist(err) { err = os.MkdirAll(filepath.Dir(filePath), 0o755) if err != nil { return fmt.Errorf("create %s: %w", filepath.Dir(filePath), err) } raw, err := json.Marshal(workspaceInfo.LastDevContainerConfig.Config) if err != nil { return fmt.Errorf("marshal devcontainer.json: %w", err) } err = os.WriteFile(filePath, raw, 0o600) if err != nil { return fmt.Errorf("write %s: %w", filePath, err) } } else if err != nil { return fmt.Errorf("error stating %s: %w", filePath, err) } return nil } func configureCredentials(ctx context.Context, cancel context.CancelFunc, workspaceInfo *provider2.AgentWorkspaceInfo, client tunnel.TunnelClient, log log.Logger) (string, string, error) { if workspaceInfo.Agent.InjectDockerCredentials != "true" && workspaceInfo.Agent.InjectGitCredentials != "true" { return "", "", nil } serverPort, err := credentials.StartCredentialsServer(ctx, cancel, client, log) if err != nil { return "", "", err } binaryPath, err := os.Executable() if err != nil { return "", "", err } if workspaceInfo.Origin == "" { return "", "", fmt.Errorf("workspace folder is not set") } dockerCredentials := "" if workspaceInfo.Agent.InjectDockerCredentials == "true" { dockerCredentials, err = dockercredentials.ConfigureCredentialsMachine(workspaceInfo.Origin, serverPort, log) if err != nil { return "", "", err } } gitCredentials := "" if workspaceInfo.Agent.InjectGitCredentials == "true" { gitCredentials = fmt.Sprintf("!'%s' agent git-credentials --port %d", binaryPath, serverPort) _ = os.Setenv("DEVPOD_GIT_HELPER_PORT", strconv.Itoa(serverPort)) } return dockerCredentials, gitCredentials, nil } func installDaemon(workspaceInfo *provider2.AgentWorkspaceInfo, log log.Logger) error { if len(workspaceInfo.Agent.Exec.Shutdown) == 0 { return nil } log.Debugf("Installing DevPod daemon into server...") err := agentdaemon.InstallDaemon(workspaceInfo.Agent.DataPath, workspaceInfo.CLIOptions.DaemonInterval, log) if err != nil { return errors.Wrap(err, "install daemon") } return nil } func downloadLocalFolder(ctx context.Context, workspaceDir string, client tunnel.TunnelClient, log log.Logger) error { log.Infof("Upload folder to server") stream, err := client.StreamWorkspace(ctx, &tunnel.Empty{}) if err != nil { return errors.Wrap(err, "read workspace") } err = extract.Extract(tunnelserver.NewStreamReader(stream, log), workspaceDir) if err != nil { return errors.Wrap(err, "extract local folder") } return nil } func prepareImage(workspaceDir, image string) error { // create a .devcontainer.json with the image err := os.WriteFile(filepath.Join(workspaceDir, ".devcontainer.json"), []byte(`{ "image": "`+image+`" }`), 0o600) if err != nil { return err } return nil } func installDocker(log log.Logger) (err error) { if !command.Exists("docker") { writer := log.Writer(logrus.InfoLevel, false) defer writer.Close() log.Debug("Installing Docker...") shellCommand := exec.Command("sh", "-c", scripts.InstallDocker) shellCommand.Stdout = writer shellCommand.Stderr = writer err = shellCommand.Run() } return err } func configureDockerDaemon(ctx context.Context, log log.Logger) (err error) { log.Info("Configuring docker daemon ...") // Enable image snapshotter in the dameon var daemonConfig = []byte(`{ "features": { "containerd-snapshotter": true } }`) // Check rootless docker homeDir, err := util.UserHomeDir() if err != nil { return err } if _, err = os.Stat(fmt.Sprintf("%s/.config/docker", homeDir)); !errors.Is(err, os.ErrNotExist) { err = os.WriteFile(fmt.Sprintf("%s/.config/docker/daemon.json", homeDir), daemonConfig, 0644) } // otherwise assume default if err != nil { if err = os.WriteFile("/etc/docker/daemon.json", daemonConfig, 0644); err != nil { return err } } // reload docker daemon return exec.CommandContext(ctx, "pkill", "-HUP", "dockerd").Run() } ================================================ FILE: cmd/agent/workspace/update_config.go ================================================ package workspace import ( "context" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // UpdateConfigCmd holds the cmd flags type UpdateConfigCmd struct { *flags.GlobalFlags WorkspaceInfo string } // NewUpdateConfigCmd creates a new command func NewUpdateConfigCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &UpdateConfigCmd{ GlobalFlags: flags, } updateConfigCmd := &cobra.Command{ Use: "update-config", Short: "Updates the workspace config", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, _ []string) error { return cmd.Run(context.Background()) }, } updateConfigCmd.Flags().StringVar(&cmd.WorkspaceInfo, "workspace-info", "", "The workspace info") _ = updateConfigCmd.MarkFlagRequired("workspace-info") return updateConfigCmd } func (cmd *UpdateConfigCmd) Run(ctx context.Context) error { // write workspace info shouldExit, _, err := agent.WriteWorkspaceInfo(cmd.WorkspaceInfo, log.Default) if err != nil { return err } else if shouldExit { return nil } return nil } ================================================ FILE: cmd/agent/workspace/workspace.go ================================================ package workspace import ( "github.com/loft-sh/devpod/cmd/flags" "github.com/spf13/cobra" ) // NewWorkspaceCmd returns a new command func NewWorkspaceCmd(flags *flags.GlobalFlags) *cobra.Command { workspaceCmd := &cobra.Command{ Use: "workspace", Short: "Workspace commands", } workspaceCmd.AddCommand(NewUpCmd(flags)) workspaceCmd.AddCommand(NewDeleteCmd(flags)) workspaceCmd.AddCommand(NewStopCmd(flags)) workspaceCmd.AddCommand(NewStatusCmd(flags)) workspaceCmd.AddCommand(NewUpdateConfigCmd(flags)) workspaceCmd.AddCommand(NewBuildCmd(flags)) workspaceCmd.AddCommand(NewLogsDaemonCmd(flags)) workspaceCmd.AddCommand(NewInstallDotfilesCmd(flags)) workspaceCmd.AddCommand(NewSetupGPGCmd(flags)) workspaceCmd.AddCommand(NewLogsCmd(flags)) return workspaceCmd } ================================================ FILE: cmd/build.go ================================================ package cmd import ( "context" "fmt" "io" "os" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" "github.com/loft-sh/devpod/pkg/agent/tunnelserver" "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/config" config2 "github.com/loft-sh/devpod/pkg/devcontainer/config" "github.com/loft-sh/devpod/pkg/image" "github.com/loft-sh/devpod/pkg/provider" workspace2 "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // BuildCmd holds the cmd flags type BuildCmd struct { *flags.GlobalFlags provider.CLIOptions ProviderOptions []string SkipDelete bool Machine string } // NewBuildCmd creates a new command func NewBuildCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &BuildCmd{ GlobalFlags: flags, } buildCmd := &cobra.Command{ Use: "build [flags] [workspace-path|workspace-name]", Short: "Builds a workspace", RunE: func(cobraCmd *cobra.Command, args []string) error { ctx := cobraCmd.Context() devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } // check permissions if !cmd.SkipPush && cmd.Repository != "" { err = image.CheckPushPermissions(cmd.Repository) if err != nil { return fmt.Errorf("cannot push to %s, please make sure you have push permissions to repository %s", cmd.Repository, cmd.Repository) } } // validate tags if len(cmd.Tag) > 0 { if err := image.ValidateTags(cmd.Tag); err != nil { return fmt.Errorf("cannot build image, %w", err) } } if devPodConfig.ContextOption(config.ContextOptionSSHStrictHostKeyChecking) == "true" { cmd.StrictHostKeyChecking = true } // create a temporary workspace exists := workspace2.Exists(ctx, devPodConfig, args, "", cmd.Owner, log.Default) sshConfigFile, err := os.CreateTemp("", "devpodssh.config") if err != nil { return err } sshConfigPath := sshConfigFile.Name() // defer removal of temporary ssh config file defer os.Remove(sshConfigPath) baseWorkspaceClient, err := workspace2.Resolve( ctx, devPodConfig, "", nil, args, "", cmd.Machine, cmd.ProviderOptions, false, cmd.DevContainerImage, cmd.DevContainerPath, sshConfigPath, nil, cmd.UID, false, cmd.Owner, log.Default, ) if err != nil { return err } // delete workspace if we have created it if exists == "" && !cmd.SkipDelete { defer func() { err = baseWorkspaceClient.Delete(ctx, client.DeleteOptions{Force: true}) if err != nil { log.Default.Errorf("Error deleting workspace: %v", err) } }() } // check if regular workspace client workspaceClient, ok := baseWorkspaceClient.(client.WorkspaceClient) if !ok { return fmt.Errorf("building is currently not supported for proxy providers") } return cmd.Run(ctx, workspaceClient) }, } buildCmd.Flags().StringVar(&cmd.DevContainerImage, "devcontainer-image", "", "The container image to use, this will override the devcontainer.json value in the project") buildCmd.Flags().StringVar(&cmd.DevContainerPath, "devcontainer-path", "", "The path to the devcontainer.json relative to the project") buildCmd.Flags().StringSliceVar(&cmd.ProviderOptions, "provider-option", []string{}, "Provider option in the form KEY=VALUE") buildCmd.Flags().BoolVar(&cmd.SkipDelete, "skip-delete", false, "If true will not delete the workspace after building it") buildCmd.Flags().StringVar(&cmd.Machine, "machine", "", "The machine to use for this workspace. The machine needs to exist beforehand or the command will fail. If the workspace already exists, this option has no effect") buildCmd.Flags().StringVar(&cmd.Repository, "repository", "", "The repository to push to") buildCmd.Flags().StringSliceVar(&cmd.Tag, "tag", []string{}, "Image Tag(s) in the form of a comma separated list --tag latest,arm64 or multiple flags --tag latest --tag arm64") buildCmd.Flags().StringSliceVar(&cmd.Platforms, "platform", []string{}, "Set target platform for build") buildCmd.Flags().BoolVar(&cmd.SkipPush, "skip-push", false, "If true will not push the image to the repository, useful for testing") buildCmd.Flags().Var(&cmd.GitCloneStrategy, "git-clone-strategy", "The git clone strategy DevPod uses to checkout git based workspaces. Can be full (default), blobless, treeless or shallow") buildCmd.Flags().BoolVar(&cmd.GitCloneRecursiveSubmodules, "git-clone-recursive-submodules", false, "If true will clone git submodule repositories recursively") // TESTING buildCmd.Flags().BoolVar(&cmd.ForceBuild, "force-build", false, "TESTING ONLY") buildCmd.Flags().BoolVar(&cmd.ForceInternalBuildKit, "force-internal-buildkit", false, "TESTING ONLY") _ = buildCmd.Flags().MarkHidden("force-build") _ = buildCmd.Flags().MarkHidden("force-internal-buildkit") return buildCmd } func (cmd *BuildCmd) Run(ctx context.Context, client client.WorkspaceClient) error { // build workspace err := cmd.build(ctx, client, log.Default) if err != nil { return err } return nil } func (cmd *BuildCmd) build(ctx context.Context, workspaceClient client.WorkspaceClient, log log.Logger) error { err := workspaceClient.Lock(ctx) if err != nil { return err } defer workspaceClient.Unlock() err = startWait(ctx, workspaceClient, true, log) if err != nil { return err } log.Infof("Building devcontainer...") defer log.Debugf("Done building devcontainer") _, err = buildAgentClient(ctx, workspaceClient, cmd.CLIOptions, "build", log) return err } func buildAgentClient(ctx context.Context, workspaceClient client.WorkspaceClient, cliOptions provider.CLIOptions, agentCommand string, log log.Logger, options ...tunnelserver.Option) (*config2.Result, error) { // compress info workspaceInfo, wInfo, err := workspaceClient.AgentInfo(cliOptions) if err != nil { return nil, err } // create container etc. command := fmt.Sprintf("'%s' agent workspace %s --workspace-info '%s'", workspaceClient.AgentPath(), agentCommand, workspaceInfo) if log.GetLevel() == logrus.DebugLevel { command += " --debug" } // create pipes stdoutReader, stdoutWriter, err := os.Pipe() if err != nil { return nil, err } stdinReader, stdinWriter, err := os.Pipe() if err != nil { return nil, err } defer stdoutWriter.Close() defer stdinWriter.Close() // start machine on stdio cancelCtx, cancel := context.WithCancel(ctx) defer cancel() errChan := make(chan error, 1) go func() { defer log.Debugf("Done executing up command") defer cancel() writer := log.ErrorStreamOnly().Writer(logrus.InfoLevel, false) defer writer.Close() errChan <- agent.InjectAgentAndExecute( cancelCtx, func(ctx context.Context, command string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { return workspaceClient.Command(ctx, client.CommandOptions{ Command: command, Stdin: stdin, Stdout: stdout, Stderr: stderr, }) }, workspaceClient.AgentLocal(), workspaceClient.AgentPath(), workspaceClient.AgentURL(), true, command, stdinReader, stdoutWriter, writer, log.ErrorStreamOnly(), wInfo.InjectTimeout) }() // create container etc. result, err := tunnelserver.RunUpServer( cancelCtx, stdoutReader, stdinWriter, workspaceClient.AgentInjectGitCredentials(cliOptions), workspaceClient.AgentInjectDockerCredentials(cliOptions), workspaceClient.WorkspaceConfig(), log, options..., ) if err != nil { return nil, errors.Wrap(err, "run tunnel machine") } // wait until command finished return result, <-errChan } ================================================ FILE: cmd/completion/suggestions.go ================================================ package completion import ( "strings" "github.com/spf13/cobra" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" ) func RegisterFlagCompletionFuns(rootCmd *cobra.Command, globalFlags *flags.GlobalFlags) error { if err := rootCmd.RegisterFlagCompletionFunc("provider", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return GetProviderSuggestions(rootCmd, globalFlags.Context, globalFlags.Provider, args, toComplete, globalFlags.Owner, log.Default) }); err != nil { return err } if err := rootCmd.RegisterFlagCompletionFunc("context", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return GetContextSuggestions(rootCmd, globalFlags.Context, globalFlags.Provider, args, toComplete, globalFlags.Owner, log.Default) }); err != nil { return err } return nil } func GetWorkspaceSuggestions(rootCmd *cobra.Command, context, provider string, args []string, toComplete string, owner platform.OwnerFilter, logger log.Logger) ([]string, cobra.ShellCompDirective) { devPodConfig, err := config.LoadConfig(context, provider) if err != nil { return nil, cobra.ShellCompDirectiveError } workspaces, err := workspace.List(rootCmd.Context(), devPodConfig, false, owner, logger) if err != nil { return nil, cobra.ShellCompDirectiveError } var suggestions []string for _, ws := range workspaces { if strings.HasPrefix(ws.ID, toComplete) { suggestions = append(suggestions, ws.ID) } } return suggestions, cobra.ShellCompDirectiveNoFileComp } func GetProviderSuggestions(rootCmd *cobra.Command, context, provider string, args []string, toComplete string, owner platform.OwnerFilter, logger log.Logger) ([]string, cobra.ShellCompDirective) { devPodConfig, err := config.LoadConfig(context, provider) if err != nil { return nil, cobra.ShellCompDirectiveError } providers, err := workspace.LoadAllProviders(devPodConfig, log.Default.ErrorStreamOnly()) if err != nil { return nil, cobra.ShellCompDirectiveError } var suggestions []string for _, provider := range providers { if strings.HasPrefix(provider.Config.Name, toComplete) { suggestions = append(suggestions, provider.Config.Name) } } return suggestions, cobra.ShellCompDirectiveNoFileComp } func GetContextSuggestions(rootCmd *cobra.Command, context, provider string, args []string, toComplete string, owner platform.OwnerFilter, logger log.Logger) ([]string, cobra.ShellCompDirective) { devPodConfig, err := config.LoadConfig(context, provider) if err != nil { return nil, cobra.ShellCompDirectiveError } var suggestions []string for contextName, _ := range devPodConfig.Contexts { if strings.HasPrefix(contextName, toComplete) { suggestions = append(suggestions, contextName) } } return suggestions, cobra.ShellCompDirectiveNoFileComp } ================================================ FILE: cmd/context/context.go ================================================ package context import ( "github.com/loft-sh/devpod/cmd/flags" "github.com/spf13/cobra" ) // NewContextCmd returns a new command func NewContextCmd(flags *flags.GlobalFlags) *cobra.Command { contextCmd := &cobra.Command{ Use: "context", Short: "DevPod Context commands", } contextCmd.AddCommand(NewCreateCmd(flags)) contextCmd.AddCommand(NewDeleteCmd(flags)) contextCmd.AddCommand(NewUseCmd(flags)) contextCmd.AddCommand(NewOptionsCmd(flags)) contextCmd.AddCommand(NewSetOptionsCmd(flags)) contextCmd.AddCommand(NewListCmd(flags)) return contextCmd } ================================================ FILE: cmd/context/create.go ================================================ package context import ( "context" "fmt" "strings" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" provider2 "github.com/loft-sh/devpod/pkg/provider" "github.com/pkg/errors" "github.com/spf13/cobra" ) // CreateCmd holds the create cmd flags type CreateCmd struct { flags.GlobalFlags Options []string } // NewCreateCmd creates a new command func NewCreateCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &CreateCmd{ GlobalFlags: *flags, } createCmd := &cobra.Command{ Use: "create", Short: "Create a new DevPod context", RunE: func(_ *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("please specify the context to create") } return cmd.Run(context.Background(), args[0]) }, } createCmd.Flags().StringArrayVarP(&cmd.Options, "option", "o", []string{}, "context option in the form KEY=VALUE") return createCmd } // Run runs the command logic func (cmd *CreateCmd) Run(ctx context.Context, context string) error { devPodConfig, err := config.LoadConfig("", cmd.Provider) if err != nil { return err } else if devPodConfig.Contexts[context] != nil { return fmt.Errorf("context '%s' already exists", context) } // verify name if provider2.ProviderNameRegEx.MatchString(context) { return fmt.Errorf("context name can only include smaller case letters, numbers or dashes") } else if len(context) > 48 { return fmt.Errorf("context name cannot be longer than 48 characters") } devPodConfig.Contexts[context] = &config.ContextConfig{} // check if there are create options set if len(cmd.Options) > 0 { err = setOptions(devPodConfig, context, cmd.Options) if err != nil { return err } } devPodConfig.DefaultContext = context err = config.SaveConfig(devPodConfig) if err != nil { return errors.Wrap(err, "save config") } return nil } func setOptions(devPodConfig *config.Config, context string, options []string) error { optionValues, err := parseOptions(options) if err != nil { return err } else if devPodConfig.Contexts[context] == nil { return fmt.Errorf("context '%s' doesn't exist", context) } newValues := map[string]config.OptionValue{} if devPodConfig.Contexts[context].Options != nil { for k, v := range devPodConfig.Contexts[context].Options { newValues[k] = v } } for k, v := range optionValues { newValues[k] = v } devPodConfig.Contexts[context].Options = newValues return nil } func parseOptions(options []string) (map[string]config.OptionValue, error) { allowedOptions := []string{} contextOptions := map[string]config.ContextOption{} for _, option := range config.ContextOptions { allowedOptions = append(allowedOptions, option.Name) contextOptions[option.Name] = option } retMap := map[string]config.OptionValue{} for _, option := range options { splitted := strings.Split(option, "=") if len(splitted) == 1 { return nil, fmt.Errorf("invalid option '%s', expected format KEY=VALUE", option) } key := strings.ToUpper(strings.TrimSpace(splitted[0])) value := strings.Join(splitted[1:], "=") contextOption, ok := contextOptions[key] if !ok { return nil, fmt.Errorf("invalid option '%s', allowed options are: %v", key, allowedOptions) } if len(contextOption.Enum) > 0 { found := false for _, e := range contextOption.Enum { if value == e { found = true break } } if !found { return nil, fmt.Errorf("invalid value '%s' for option '%s', has to match one of the following values: %v", value, key, contextOption.Enum) } } retMap[key] = config.OptionValue{ Value: value, UserProvided: true, } } return retMap, nil } ================================================ FILE: cmd/context/delete.go ================================================ package context import ( "context" "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/pkg/errors" "github.com/spf13/cobra" ) // DeleteCmd holds the delete cmd flags type DeleteCmd struct { flags.GlobalFlags } // NewDeleteCmd deletes a new command func NewDeleteCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &DeleteCmd{ GlobalFlags: *flags, } deleteCmd := &cobra.Command{ Use: "delete", Short: "Delete a DevPod context", RunE: func(_ *cobra.Command, args []string) error { if len(args) > 1 { return fmt.Errorf("please specify the context to delete") } devPodContext := "" if len(args) == 1 { devPodContext = args[0] } return cmd.Run(context.Background(), devPodContext) }, } return deleteCmd } // Run runs the command logic func (cmd *DeleteCmd) Run(ctx context.Context, context string) error { devPodConfig, err := config.LoadConfig(context, cmd.Provider) if err != nil { return err } // check for context if context == "" { context = devPodConfig.DefaultContext } else if devPodConfig.Contexts[context] == nil { return fmt.Errorf("context '%s' doesn't exist", context) } // check for default context if context == "default" { return fmt.Errorf("cannot delete 'default' context") } delete(devPodConfig.Contexts, context) if devPodConfig.DefaultContext == context { devPodConfig.DefaultContext = "default" } if devPodConfig.OriginalContext == context { devPodConfig.OriginalContext = "default" } err = config.SaveConfig(devPodConfig) if err != nil { return errors.Wrap(err, "save config") } return nil } ================================================ FILE: cmd/context/list.go ================================================ package context import ( "context" "encoding/json" "fmt" "sort" "strconv" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/log" "github.com/loft-sh/log/table" "github.com/spf13/cobra" ) // ListCmd holds the list cmd flags type ListCmd struct { flags.GlobalFlags Output string } // NewListCmd creates a new command func NewListCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &ListCmd{ GlobalFlags: *flags, } listCmd := &cobra.Command{ Use: "list", Aliases: []string{"ls"}, Short: "List DevPod contexts", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background()) }, } listCmd.Flags().StringVar(&cmd.Output, "output", "plain", "The output format to use. Can be json or plain") return listCmd } type ContextWithDefault struct { Name string `json:"name,omitempty"` Default bool `json:"default,omitempty"` } // Run runs the command logic func (cmd *ListCmd) Run(ctx context.Context) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } if cmd.Output == "plain" { tableEntries := [][]string{} for contextName := range devPodConfig.Contexts { tableEntries = append(tableEntries, []string{ contextName, strconv.FormatBool(devPodConfig.DefaultContext == contextName), }) } sort.SliceStable(tableEntries, func(i, j int) bool { return tableEntries[i][0] < tableEntries[j][0] }) table.PrintTable(log.Default, []string{ "Name", "Default", }, tableEntries) } else if cmd.Output == "json" { ides := []ContextWithDefault{} for contextName := range devPodConfig.Contexts { ides = append(ides, ContextWithDefault{ Name: contextName, Default: devPodConfig.DefaultContext == contextName, }) } out, err := json.MarshalIndent(ides, "", " ") if err != nil { return err } fmt.Print(string(out)) } else { return fmt.Errorf("unexpected output format, choose either json or plain. Got %s", cmd.Output) } return nil } ================================================ FILE: cmd/context/options.go ================================================ package context import ( "context" "encoding/json" "fmt" "sort" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/log" "github.com/loft-sh/log/table" "github.com/spf13/cobra" ) // OptionsCmd holds the options cmd flags type OptionsCmd struct { *flags.GlobalFlags Output string } // NewOptionsCmd creates a new command func NewOptionsCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &OptionsCmd{ GlobalFlags: flags, } optionsCmd := &cobra.Command{ Use: "options", Short: "Show options of a context", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } optionsCmd.Flags().StringVar(&cmd.Output, "output", "plain", "The output format to use. Can be json or plain") return optionsCmd } type optionWithValue struct { config.ContextOption `json:",inline"` Value string `json:"value,omitempty"` } // Run runs the command logic func (cmd *OptionsCmd) Run(ctx context.Context, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, "") if err != nil { return err } entryOptions := devPodConfig.Current().Options if entryOptions == nil { entryOptions = map[string]config.OptionValue{} } if cmd.Output == "plain" { tableEntries := [][]string{} for _, entry := range config.ContextOptions { value := entryOptions[entry.Name].Value tableEntries = append(tableEntries, []string{ entry.Name, entry.Description, entry.Default, value, }) } sort.SliceStable(tableEntries, func(i, j int) bool { return tableEntries[i][0] < tableEntries[j][0] }) table.PrintTable(log.Default, []string{ "Name", "Description", "Default", "Value", }, tableEntries) } else if cmd.Output == "json" { options := map[string]optionWithValue{} for _, entry := range config.ContextOptions { options[entry.Name] = optionWithValue{ ContextOption: entry, Value: entryOptions[entry.Name].Value, } } out, err := json.MarshalIndent(options, "", " ") if err != nil { return err } fmt.Print(string(out)) } else { return fmt.Errorf("unexpected output format, choose either json or plain. Got %s", cmd.Output) } return nil } ================================================ FILE: cmd/context/set_options.go ================================================ package context import ( "context" "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/pkg/errors" "github.com/spf13/cobra" ) // SetOptionsCmd holds the setOptions cmd flags type SetOptionsCmd struct { flags.GlobalFlags Options []string } // NewSetOptionsCmd setOptionss a new command func NewSetOptionsCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &SetOptionsCmd{ GlobalFlags: *flags, } setOptionsCmd := &cobra.Command{ Use: "set-options", Short: "Set options for a DevPod context", RunE: func(_ *cobra.Command, args []string) error { if len(args) > 1 { return fmt.Errorf("please specify the context") } devPodContext := "" if len(args) == 1 { devPodContext = args[0] } return cmd.Run(context.Background(), devPodContext) }, } setOptionsCmd.Flags().StringArrayVarP(&cmd.Options, "option", "o", []string{}, "context option in the form KEY=VALUE") return setOptionsCmd } // Run runs the command logic func (cmd *SetOptionsCmd) Run(ctx context.Context, context string) error { devPodConfig, err := config.LoadConfig("", cmd.Provider) if err != nil { return err } // check for context if context == "" { context = devPodConfig.DefaultContext } else if devPodConfig.Contexts[context] == nil { return fmt.Errorf("context '%s' doesn't exist", context) } // check if there are setOptions options set if len(cmd.Options) > 0 { err = setOptions(devPodConfig, context, cmd.Options) if err != nil { return err } } err = config.SaveConfig(devPodConfig) if err != nil { return errors.Wrap(err, "save config") } return nil } ================================================ FILE: cmd/context/use.go ================================================ package context import ( "context" "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/pkg/errors" "github.com/spf13/cobra" ) // UseCmd holds the use cmd flags type UseCmd struct { flags.GlobalFlags Options []string } // NewUseCmd uses a new command func NewUseCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &UseCmd{ GlobalFlags: *flags, } useCmd := &cobra.Command{ Use: "use", Short: "Set a DevPod context as the default", RunE: func(_ *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("please specify the context to use") } return cmd.Run(context.Background(), args[0]) }, } useCmd.Flags().StringArrayVarP(&cmd.Options, "option", "o", []string{}, "context option in the form KEY=VALUE") return useCmd } // Run runs the command logic func (cmd *UseCmd) Run(ctx context.Context, context string) error { devPodConfig, err := config.LoadConfig("", cmd.Provider) if err != nil { return err } else if devPodConfig.Contexts[context] == nil { return fmt.Errorf("context '%s' doesn't exist", context) } // check if there are use options set if len(cmd.Options) > 0 { err = setOptions(devPodConfig, context, cmd.Options) if err != nil { return err } } devPodConfig.DefaultContext = context err = config.SaveConfig(devPodConfig) if err != nil { return errors.Wrap(err, "save config") } return nil } ================================================ FILE: cmd/delete.go ================================================ package cmd import ( "context" "fmt" "github.com/loft-sh/devpod/cmd/completion" "github.com/loft-sh/devpod/cmd/flags" client2 "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // DeleteCmd holds the delete cmd flags type DeleteCmd struct { *flags.GlobalFlags client2.DeleteOptions } // NewDeleteCmd creates a new command func NewDeleteCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &DeleteCmd{ GlobalFlags: flags, } deleteCmd := &cobra.Command{ Use: "delete [flags] [workspace-path|workspace-name]", Short: "Deletes an existing workspace", Long: `Deletes an existing workspace. You can specify the workspace by its path or name. If the workspace is not found, you can use the --ignore-not-found flag to treat it as a successful delete.`, RunE: func(_ *cobra.Command, args []string) error { _, err := clientimplementation.DecodeOptionsFromEnv(clientimplementation.DevPodFlagsDelete, &cmd.DeleteOptions) if err != nil { return fmt.Errorf("decode up options: %w", err) } ctx := context.Background() devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } err = clientimplementation.DecodePlatformOptionsFromEnv(&cmd.Platform) if err != nil { return fmt.Errorf("decode platform options: %w", err) } return cmd.Run(ctx, devPodConfig, args) }, ValidArgsFunction: func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetWorkspaceSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, log.Default) }, } deleteCmd.Flags().BoolVar(&cmd.IgnoreNotFound, "ignore-not-found", false, "Treat \"workspace not found\" as a successful delete") deleteCmd.Flags().StringVar(&cmd.GracePeriod, "grace-period", "", "The amount of time to give the command to delete the workspace") deleteCmd.Flags().BoolVar(&cmd.Force, "force", false, "Delete workspace even if it is not found remotely anymore") return deleteCmd } // Run runs the command logic func (cmd *DeleteCmd) Run(ctx context.Context, devPodConfig *config.Config, args []string) error { if len(args) == 0 { workspaceName, err := workspace.Delete(ctx, devPodConfig, args, cmd.IgnoreNotFound, cmd.Force, cmd.DeleteOptions, cmd.Owner, log.Default) if err != nil { return err } log.Default.Donef("Successfully deleted workspace '%s'", workspaceName) return nil } for _, arg := range args { workspaceName, err := workspace.Delete(ctx, devPodConfig, []string{arg}, cmd.IgnoreNotFound, cmd.Force, cmd.DeleteOptions, cmd.Owner, log.Default) if err != nil { log.Default.Errorf("Failed to delete workspace '%s': %v", arg, err) continue } log.Default.Donef("Successfully deleted workspace '%s'", workspaceName) } return nil } ================================================ FILE: cmd/export.go ================================================ package cmd import ( "context" "encoding/json" "fmt" "github.com/loft-sh/devpod/cmd/completion" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/provider" workspace2 "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // ExportCmd holds the export cmd flags type ExportCmd struct { *flags.GlobalFlags } // NewExportCmd creates a new command func NewExportCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &ExportCmd{ GlobalFlags: flags, } exportCmd := &cobra.Command{ Use: "export [flags] [workspace-path|workspace-name]", Short: "Exports a workspace configuration", Hidden: true, RunE: func(_ *cobra.Command, args []string) error { ctx := context.Background() devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } return cmd.Run(ctx, devPodConfig, args) }, ValidArgsFunction: func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetWorkspaceSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, log.Default) }, } return exportCmd } // Run runs the command logic func (cmd *ExportCmd) Run(ctx context.Context, devPodConfig *config.Config, args []string) error { // try to load workspace logger := log.Default.ErrorStreamOnly() client, err := workspace2.Get(ctx, devPodConfig, args, false, cmd.Owner, false, logger) if err != nil { return err } // export workspace exportConfig, err := exportWorkspace(devPodConfig, client.WorkspaceConfig()) if err != nil { return err } // marshal config out, err := json.Marshal(exportConfig) if err != nil { return err } fmt.Println(string(out)) return nil } func exportWorkspace(devPodConfig *config.Config, workspaceConfig *provider.Workspace) (*provider.ExportConfig, error) { var err error // create return config retConfig := &provider.ExportConfig{} // export workspace retConfig.Workspace, err = provider.ExportWorkspace(workspaceConfig.Context, workspaceConfig.ID) if err != nil { return nil, fmt.Errorf("export workspace config: %w", err) } // has machine? if workspaceConfig.Machine.ID != "" { retConfig.Machine, err = provider.ExportMachine(workspaceConfig.Context, workspaceConfig.Machine.ID) if err != nil { return nil, fmt.Errorf("export machine config: %w", err) } } // export provider retConfig.Provider, err = provider.ExportProvider(devPodConfig, workspaceConfig.Context, workspaceConfig.Provider.Name) if err != nil { return nil, fmt.Errorf("export provider config: %w", err) } return retConfig, nil } ================================================ FILE: cmd/flags/flags.go ================================================ package flags import ( "github.com/loft-sh/devpod/pkg/platform" flag "github.com/spf13/pflag" ) type GlobalFlags struct { Context string Provider string AgentDir string DevPodHome string UID string Owner platform.OwnerFilter LogOutput string Debug bool Silent bool } // SetGlobalFlags applies the global flags func SetGlobalFlags(flags *flag.FlagSet) *GlobalFlags { globalFlags := &GlobalFlags{} flags.StringVar(&globalFlags.DevPodHome, "devpod-home", "", "If defined will override the default devpod home. You can also use DEVPOD_HOME to set this") flags.StringVar(&globalFlags.LogOutput, "log-output", "plain", "The log format to use. Can be either plain, raw or json") flags.StringVar(&globalFlags.Context, "context", "", "The context to use") flags.StringVar(&globalFlags.Provider, "provider", "", "The provider to use. Needs to be configured for the selected context.") flags.BoolVar(&globalFlags.Debug, "debug", false, "Prints the stack trace if an error occurs") flags.BoolVar(&globalFlags.Silent, "silent", false, "Run in silent mode and prevents any devpod log output except panics & fatals") flags.Var(&globalFlags.Owner, "owner", "Show pro workspaces for owner") _ = flags.MarkHidden("owner") flags.StringVar(&globalFlags.UID, "uid", "", "Set UID for workspace") _ = flags.MarkHidden("uid") flags.StringVar(&globalFlags.AgentDir, "agent-dir", "", "The data folder where agent data is stored.") _ = flags.MarkHidden("agent-dir") return globalFlags } ================================================ FILE: cmd/helper/check_provider_update.go ================================================ package helper import ( "bytes" "context" "encoding/json" "fmt" "strings" "github.com/blang/semver" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" ) var ( errProviderNotFound = errors.New("provider not found") ) type CheckProviderUpdateCmd struct { *flags.GlobalFlags log log.Logger } type providerVersionCheck struct { UpdateAvailable bool `json:"updateAvailable"` LatestVersion string `json:"latestVersion,omitempty"` } // NewCheckProviderUpdateCmd creates a new command func NewCheckProviderUpdateCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &CheckProviderUpdateCmd{ GlobalFlags: flags, log: log.Default, } shellCmd := &cobra.Command{ Use: "check-provider-update", Short: "Check if a provider update is available", RunE: func(_ *cobra.Command, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } return cmd.Run(context.Background(), devPodConfig, args) }, } return shellCmd } func (cmd *CheckProviderUpdateCmd) Run(ctx context.Context, devPodConfig *config.Config, args []string) error { if len(args) != 1 { return fmt.Errorf("provider is missing") } providerName := args[0] providerSourceRaw, err := workspace.ResolveProviderSource(devPodConfig, providerName, cmd.log) if err != nil { return fmt.Errorf("provider %s doesn't exist", providerName) } // retrieve current config for provider allProviders, err := workspace.LoadAllProviders(devPodConfig, cmd.log) if err != nil { return err } currentProvider, ok := allProviders[providerName] if !ok { return errProviderNotFound } latestProviderConfig, err := loadLatestProvider(providerSourceRaw, cmd.log) if err != nil { return err } currentProviderVersion, err := semver.Parse(strings.TrimPrefix(currentProvider.Config.Version, "v")) if err != nil { return err } latestProviderVersion, err := semver.Parse(strings.TrimPrefix(latestProviderConfig.Version, "v")) if err != nil { return err } versionCheck := providerVersionCheck{UpdateAvailable: false} // check if new version is newer if latestProviderVersion.GT(currentProviderVersion) { versionCheck.UpdateAvailable = true versionCheck.LatestVersion = latestProviderConfig.Version } out, err := json.Marshal(versionCheck) if err != nil { return err } fmt.Println(string(out)) return nil } func loadLatestProvider(providerSourceRaw string, log log.Logger) (*provider.ProviderConfig, error) { providerRaw, _, err := workspace.ResolveProvider(providerSourceRaw, log) if err != nil { return nil, errors.Wrap(err, "resolve provider") } providerConfig, err := provider.ParseProvider(bytes.NewReader(providerRaw)) if err != nil { return nil, errors.Wrap(err, "parse provider") } return providerConfig, nil } ================================================ FILE: cmd/helper/docker_credentials.go ================================================ package helper import ( "fmt" "os/user" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/credentials" "github.com/loft-sh/devpod/pkg/dockercredentials" "github.com/loft-sh/log" "github.com/spf13/cobra" ) type DockerCredentialsHelperCmd struct { *flags.GlobalFlags WorkspaceID string } func NewDockerCredentialsHelperCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &DockerCredentialsHelperCmd{ GlobalFlags: flags, } fleetCmd := &cobra.Command{ Use: "setup-docker-credentials-helper", Short: "Setup the docker credentials helper manually", Args: cobra.NoArgs, RunE: cmd.Run, } return fleetCmd } func (c *DockerCredentialsHelperCmd) Run(cmd *cobra.Command, _ []string) error { u, err := user.Current() if err != nil { return fmt.Errorf("get current user: %w", err) } port, err := credentials.GetPort() if err != nil { return fmt.Errorf("get port: %w", err) } return dockercredentials.ConfigureCredentialsContainer(u.Name, port, log.Default) } ================================================ FILE: cmd/helper/fleet_helper.go ================================================ package helper import ( "os" "path/filepath" "regexp" "strings" "time" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" "github.com/spf13/cobra" ) // FleetServerCmd holds the fleet server cmd flags type FleetServerCmd struct { *flags.GlobalFlags WorkspaceID string } // NewFleetServerCmd creates a new fleet command func NewFleetServerCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &FleetServerCmd{ GlobalFlags: flags, } fleetCmd := &cobra.Command{ Use: "fleet-server", Short: "Monitor fleet server activity", Args: cobra.NoArgs, RunE: cmd.Run, } fleetCmd.Flags().StringVar(&cmd.WorkspaceID, "workspaceid", "", "Fleet WorkspaceID to monitor") return fleetCmd } // Run runs the command logic func (c *FleetServerCmd) Run(cmd *cobra.Command, _ []string) error { logFile := filepath.Join(os.Getenv("HOME"), ".cache/JetBrains/Fleet/log/fleet.log") firstConnection := regexp.MustCompile(`.*Received authorization request.*`) connStatus := regexp.MustCompile(`.*Notify.*`) for { select { case <-time.After(time.Second * 10): log, err := os.ReadFile(logFile) if err != nil { continue } // check if we had at least one fleet client connection, before // this point, we don't check for connected/disconnected strings initialized := firstConnection.FindStringSubmatch(string(log)) if len(initialized) == 0 { continue } connString := connStatus.FindAllStringSubmatch(string(log), -1) // if ouf last occurrence of notify if "Notify ID connected" // we have an active session, so let's keep alive if strings.Contains(connString[len(connString)-1][0], "is connected") { file, _ := os.Create(agent.ContainerActivityFile) file.Close() } case <-cmd.Context().Done(): //context is done - either canceled or time is up for timeout return nil } } } ================================================ FILE: cmd/helper/get_image.go ================================================ package helper import ( "context" "encoding/json" "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/image" "github.com/spf13/cobra" ) type GetImageCommand struct { *flags.GlobalFlags } // NewGetImageCmd creates a new command func NewGetImageCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &GetImageCommand{ GlobalFlags: flags, } shellCmd := &cobra.Command{ Use: "get-image [image-name]", Short: "Retrieve details about an image", RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), args) }, } return shellCmd } func (cmd *GetImageCommand) Run(ctx context.Context, args []string) error { if len(args) != 1 { return fmt.Errorf("image name is missing") } img, err := image.GetImage(ctx, args[0]) if err != nil { return err } out, err := json.MarshalIndent(img, "", " ") if err != nil { return err } fmt.Println(string(out)) return nil } ================================================ FILE: cmd/helper/get_provider_name.go ================================================ package helper import ( "bytes" "context" "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" ) type GetProviderNameCmd struct { *flags.GlobalFlags } // NewGetProviderNameCmd creates a new command func NewGetProviderNameCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &GetProviderNameCmd{ GlobalFlags: flags, } shellCmd := &cobra.Command{ Use: "get-provider-name", Short: "Retrieves a provider name", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } return shellCmd } func (cmd *GetProviderNameCmd) Run(ctx context.Context, args []string) error { if len(args) != 1 { return fmt.Errorf("provider is missing") } providerRaw, _, err := workspace.ResolveProvider(args[0], log.Default.ErrorStreamOnly()) if err != nil { return errors.Wrap(err, "resolve provider") } providerConfig, err := provider.ParseProvider(bytes.NewReader(providerRaw)) if err != nil { return errors.Wrap(err, "parse provider") } fmt.Print(providerConfig.Name) return nil } ================================================ FILE: cmd/helper/get_workspace_config.go ================================================ package helper import ( "context" "encoding/json" "fmt" "os" "time" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/devcontainer" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) type GetWorkspaceConfigCommand struct { *flags.GlobalFlags timeout time.Duration maxDepth int } type GetWorkspaceConfigCommandResult struct { IsImage bool `json:"isImage"` IsGitRepository bool `json:"isGitRepository"` IsLocal bool `json:"isLocal"` ConfigPaths []string `json:"configPaths"` } // NewGetWorkspaceConfigCommand creates a new command func NewGetWorkspaceConfigCommand(flags *flags.GlobalFlags) *cobra.Command { cmd := &GetWorkspaceConfigCommand{ GlobalFlags: flags, } shellCmd := &cobra.Command{ Use: "get-workspace-config", Short: "Retrieves a workspace config", RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } if cmd.maxDepth < 0 { log.Default.Debugf("--max-depth was %d, setting to 0", cmd.maxDepth) cmd.maxDepth = 0 } return cmd.Run(cobraCmd.Context(), devPodConfig, args) }, } shellCmd.Flags().DurationVar(&cmd.timeout, "timeout", 10*time.Second, "Timeout for the command, 10 seconds by default") shellCmd.Flags().IntVar(&cmd.maxDepth, "max-depth", 3, "Maximum depth to search for devcontainer files") return shellCmd } func (cmd *GetWorkspaceConfigCommand) Run(ctx context.Context, devPodConfig *config.Config, args []string) error { if len(args) != 1 { return fmt.Errorf("workspace source is missing") } rawSource := args[0] level := log.Default.GetLevel() if cmd.GlobalFlags.Debug { level = logrus.DebugLevel } var logger log.Logger = log.NewStdoutLogger(os.Stdin, os.Stdout, os.Stderr, level) if os.Getenv("DEVPOD_UI") == "true" { logger = log.Discard } logger.Debugf("Resolving devcontainer config for source: %s", rawSource) ctx, cancel := context.WithTimeout(context.Background(), cmd.timeout) defer cancel() done := make(chan *devcontainer.GetWorkspaceConfigResult, 1) errChan := make(chan error, 1) tmpDir, err := os.MkdirTemp("", "devpod") if err != nil { return err } defer func() { _ = os.RemoveAll(tmpDir) }() go func() { result, err := devcontainer.FindDevcontainerFiles(ctx, rawSource, tmpDir, cmd.maxDepth, devPodConfig.ContextOption(config.ContextOptionSSHStrictHostKeyChecking) == "true", logger) if err != nil { errChan <- err return } done <- result }() select { case err := <-errChan: return fmt.Errorf("unable to find devcontainer files: %w", err) case <-ctx.Done(): return fmt.Errorf("timeout while searching for devcontainer files") case result := <-done: out, err := json.Marshal(result) if err != nil { return err } fmt.Println(string(out)) } defer close(done) return nil } ================================================ FILE: cmd/helper/get_workspace_name.go ================================================ package helper import ( "context" "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/file" "github.com/loft-sh/devpod/pkg/workspace" "github.com/spf13/cobra" ) type GetWorkspaceNameCommand struct { *flags.GlobalFlags } // NewGetWorkspaceNameCmd creates a new command func NewGetWorkspaceNameCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &GetWorkspaceNameCommand{ GlobalFlags: flags, } shellCmd := &cobra.Command{ Use: "get-workspace-name", Short: "Retrieves a workspace name", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } return shellCmd } func (cmd *GetWorkspaceNameCommand) Run(ctx context.Context, args []string) error { if len(args) != 1 { return fmt.Errorf("workspace is missing") } _, name := file.IsLocalDir(args[0]) workspaceID := workspace.ToID(name) fmt.Print(workspaceID) return nil } ================================================ FILE: cmd/helper/get_workspace_uid.go ================================================ package helper import ( "context" "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/encoding" "github.com/spf13/cobra" ) type GetWorkspaceUIDCommand struct { *flags.GlobalFlags } // NewGetWorkspaceUIDCmd creates a new command func NewGetWorkspaceUIDCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &GetWorkspaceUIDCommand{ GlobalFlags: flags, } shellCmd := &cobra.Command{ Use: "get-workspace-uid", Short: "Retrieves a workspace uid", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } return shellCmd } func (cmd *GetWorkspaceUIDCommand) Run(ctx context.Context, args []string) error { fmt.Print(encoding.CreateNewUID("", "")) return nil } ================================================ FILE: cmd/helper/helper.go ================================================ package helper import ( "github.com/loft-sh/devpod/cmd/agent" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/cmd/helper/http" "github.com/loft-sh/devpod/cmd/helper/json" "github.com/loft-sh/devpod/cmd/helper/strings" "github.com/spf13/cobra" ) // NewHelperCmd returns a new command func NewHelperCmd(globalFlags *flags.GlobalFlags) *cobra.Command { helperCmd := &cobra.Command{ Use: "helper", Short: "DevPod Utility Commands", PersistentPreRunE: func(cobraCmd *cobra.Command, args []string) error { return agent.AgentPersistentPreRunE(cobraCmd, args, globalFlags) }, Hidden: true, } helperCmd.AddCommand(http.NewHTTPCmd(globalFlags)) helperCmd.AddCommand(json.NewJSONCmd(globalFlags)) helperCmd.AddCommand(strings.NewStringsCmd(globalFlags)) helperCmd.AddCommand(NewSSHServerCmd(globalFlags)) helperCmd.AddCommand(NewGetWorkspaceNameCmd(globalFlags)) helperCmd.AddCommand(NewGetWorkspaceUIDCmd(globalFlags)) helperCmd.AddCommand(NewGetWorkspaceConfigCommand(globalFlags)) helperCmd.AddCommand(NewGetProviderNameCmd(globalFlags)) helperCmd.AddCommand(NewCheckProviderUpdateCmd(globalFlags)) helperCmd.AddCommand(NewSSHClientCmd()) helperCmd.AddCommand(NewShellCmd()) helperCmd.AddCommand(NewSSHGitCloneCmd()) helperCmd.AddCommand(NewFleetServerCmd(globalFlags)) helperCmd.AddCommand(NewDockerCredentialsHelperCmd(globalFlags)) helperCmd.AddCommand(NewGetImageCmd(globalFlags)) return helperCmd } ================================================ FILE: cmd/helper/http/http.go ================================================ package http import ( "github.com/loft-sh/devpod/cmd/flags" "github.com/spf13/cobra" ) // NewHTTPCmd returns a new command func NewHTTPCmd(flags *flags.GlobalFlags) *cobra.Command { httpCmd := &cobra.Command{ Use: "http", Short: "DevPod HTTP Utility Commands", Hidden: true, } httpCmd.AddCommand(NewRequestCmd()) return httpCmd } ================================================ FILE: cmd/helper/http/request.go ================================================ package http import ( "context" "fmt" "io" "net/http" "os" "strings" devpodhttp "github.com/loft-sh/devpod/pkg/http" "github.com/pkg/errors" "github.com/spf13/cobra" ) type RequestCmd struct { Method string Data string Headers []string FailOnErrorCode bool } // NewRequestCmd creates a new ssh command func NewRequestCmd() *cobra.Command { cmd := &RequestCmd{} requestCmd := &cobra.Command{ Use: "request", Short: "Executes a http(s) request", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } requestCmd.Flags().StringVarP(&cmd.Data, "data", "d", "", "Request Data") requestCmd.Flags().StringVarP(&cmd.Method, "request", "X", "GET", "Request Type") requestCmd.Flags().StringSliceVarP(&cmd.Headers, "header", "H", []string{}, "Extra Headers") requestCmd.Flags().BoolVar(&cmd.FailOnErrorCode, "fail-on-error-code", true, "Let this command fail if the remote is returning an error code") return requestCmd } func (cmd *RequestCmd) Run(ctx context.Context, args []string) error { if len(args) == 0 { return fmt.Errorf("expected request url as argument") } cmd.Method = strings.ToUpper(cmd.Method) httpHeader := http.Header{} for _, header := range cmd.Headers { splitted := strings.Split(header, ":") if len(splitted) == 1 { return fmt.Errorf("unexpected header '%s', expected form 'HEADER: VALUE'", header) } httpHeader.Add(strings.TrimSpace(splitted[0]), strings.TrimSpace(strings.Join(splitted[1:], ":"))) } request, err := http.NewRequest(cmd.Method, args[0], strings.NewReader(cmd.Data)) if err != nil { return err } request.Header = httpHeader resp, err := devpodhttp.GetHTTPClient().Do(request) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 300 { out, _ := io.ReadAll(resp.Body) _, _ = fmt.Fprint(os.Stderr, string(out)) return fmt.Errorf("unexpected response code %d", resp.StatusCode) } _, err = io.Copy(os.Stdout, resp.Body) if err != nil { return errors.Wrap(err, "read response") } return nil } ================================================ FILE: cmd/helper/json/get.go ================================================ package json import ( "context" "encoding/json" "fmt" "io" "os" "strings" "github.com/PaesslerAG/jsonpath" "github.com/spf13/cobra" ) type GetCmd struct { File string Fail bool } // NewGetCmd creates a new ssh command func NewGetCmd() *cobra.Command { cmd := &GetCmd{} getCmd := &cobra.Command{ Use: "get", Short: "Retrieves a JSON value by JSONPath", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } getCmd.Flags().StringVarP(&cmd.File, "file", "f", "", "Parse this json file instead of STDIN") getCmd.Flags().BoolVar(&cmd.Fail, "fail", false, "Fail if value is not found") return getCmd } func (cmd *GetCmd) Run(ctx context.Context, args []string) error { if len(args) == 0 { return fmt.Errorf("jsonpath expected") } if !strings.HasPrefix(args[0], "$") { if !strings.HasPrefix(args[0], "[") && !strings.HasPrefix(args[0], ".") { args[0] = "." + args[0] } args[0] = "$" + args[0] } var jsonBytes []byte if cmd.File != "" { var err error jsonBytes, err = os.ReadFile(cmd.File) if err != nil { return err } } else { var err error jsonBytes, err = io.ReadAll(os.Stdin) if err != nil { return err } } v := interface{}(nil) err := json.Unmarshal(jsonBytes, &v) if err != nil { return fmt.Errorf("parse json") } val, err := jsonpath.Get(args[0], v) if err != nil { if cmd.Fail { return err } return nil } switch t := val.(type) { case string: fmt.Print(strings.TrimSpace(t)) return nil case bool, int, int64, rune: fmt.Print(t) return nil } out, err := json.MarshalIndent(val, "", " ") if err != nil { return err } fmt.Print(string(out)) return nil } ================================================ FILE: cmd/helper/json/json.go ================================================ package json import ( "github.com/loft-sh/devpod/cmd/flags" "github.com/spf13/cobra" ) // NewJSONCmd returns a new command func NewJSONCmd(flags *flags.GlobalFlags) *cobra.Command { jsonCmd := &cobra.Command{ Use: "json", Short: "DevPod JSON Utility Commands", Hidden: true, } jsonCmd.AddCommand(NewGetCmd()) return jsonCmd } ================================================ FILE: cmd/helper/sh.go ================================================ package helper import ( "context" "fmt" "os" "github.com/loft-sh/devpod/pkg/shell" "github.com/spf13/cobra" ) type ShellCommand struct { Command string Login bool } // NewShellCmd creates a new command func NewShellCmd() *cobra.Command { cmd := &ShellCommand{} shellCmd := &cobra.Command{ Use: "sh", Short: "Executes a command in a shell", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } shellCmd.Flags().BoolVarP(&cmd.Login, "login", "l", false, "If login shell should be used") shellCmd.Flags().StringVarP(&cmd.Command, "command", "c", "", "Command to execute") return shellCmd } func (cmd *ShellCommand) Run(ctx context.Context, args []string) error { if cmd.Command == "" && len(args) == 0 { return nil } else if cmd.Command != "" && len(args) > 0 { return fmt.Errorf("either use -c or provide a script file") } else if len(args) > 1 { return fmt.Errorf("only a single script file can be used") } // load command from file if len(args) > 0 { content, err := os.ReadFile(args[0]) if err != nil { return err } cmd.Command = string(content) } return shell.RunEmulatedShell(ctx, cmd.Command, os.Stdin, os.Stdout, os.Stderr, os.Environ()) } ================================================ FILE: cmd/helper/ssh_client.go ================================================ package helper import ( "context" "os" command2 "github.com/loft-sh/devpod/pkg/command" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" ) type SSHClient struct { Address string KeyFile string User string } // NewSSHClientCmd creates a new ssh command func NewSSHClientCmd() *cobra.Command { cmd := &SSHClient{} sshCmd := &cobra.Command{ Use: "ssh-client", Short: "Starts a new ssh client session", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } sshCmd.Flags().StringVar(&cmd.KeyFile, "key-file", "", "SSH Key file to use") sshCmd.Flags().StringVar(&cmd.Address, "address", "", "Address to connect to") sshCmd.Flags().StringVar(&cmd.User, "user", "root", "User to connect as") _ = sshCmd.MarkFlagRequired("address") return sshCmd } func (cmd *SSHClient) Run(ctx context.Context, args []string) error { sshConfig, err := cmd.getConfig() if err != nil { return err } sshClient, err := ssh.Dial("tcp", cmd.Address, sshConfig) if err != nil { return err } defer sshClient.Close() sess, err := sshClient.NewSession() if err != nil { return err } defer sess.Close() sess.Stdin = os.Stdin sess.Stdout = os.Stdout sess.Stderr = os.Stderr err = sess.Run(command2.Quote(args)) if err != nil { return err } return nil } func (cmd *SSHClient) getConfig() (*ssh.ClientConfig, error) { clientConfig := &ssh.ClientConfig{ User: cmd.User, Auth: []ssh.AuthMethod{}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), } // key file authentication? if cmd.KeyFile != "" { out, err := os.ReadFile(cmd.KeyFile) if err != nil { return nil, errors.Wrap(err, "read private ssh key") } signer, err := ssh.ParsePrivateKey(out) if err != nil { return nil, errors.Wrap(err, "parse private key") } clientConfig.Auth = append(clientConfig.Auth, ssh.PublicKeys(signer)) } return clientConfig, nil } ================================================ FILE: cmd/helper/ssh_git_clone.go ================================================ package helper import ( "context" "fmt" "net" "os" "strings" command2 "github.com/loft-sh/devpod/pkg/command" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" ) type SSHGitClone struct { KeyFiles []string Port string } func NewSSHGitCloneCmd() *cobra.Command { cmd := &SSHGitClone{} sshCmd := &cobra.Command{ Use: "ssh-git-clone", Short: "Drop-in ssh replacement in GIT_SSH_COMMAND", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } sshCmd.Flags().StringArrayVar(&cmd.KeyFiles, "key-file", []string{}, "SSH Key file to use") sshCmd.Flags().StringVar(&cmd.Port, "port", "22", "SSH port to use, defaults to 22") _ = sshCmd.MarkFlagRequired("key-file") return sshCmd } func (cmd *SSHGitClone) Run(ctx context.Context, args []string) error { if len(args) < 2 { return fmt.Errorf("expected args in format: {user}@{host} {commands...}, received \"%s\"", strings.Join(args, " ")) } host := args[0] sshCmdArgs := args[1:] if len(host) == 0 || len(sshCmdArgs) == 0 { return fmt.Errorf("unexpected input: host: %s, args: %s", host, strings.Join(sshCmdArgs, " ")) } user, addr, err := parseSSHHost(host) if err != nil { return err } sshConfig, err := getConfig(user, cmd.KeyFiles) if err != nil { return err } sshClient, err := ssh.Dial("tcp", net.JoinHostPort(addr, cmd.Port), sshConfig) if err != nil { return err } defer sshClient.Close() sess, err := sshClient.NewSession() if err != nil { return err } defer sess.Close() sess.Stdin = os.Stdin sess.Stdout = os.Stdout sess.Stderr = os.Stderr err = sess.Run(command2.Quote(sshCmdArgs)) if err != nil { return err } return nil } func getConfig(userName string, keyFilePaths []string) (*ssh.ClientConfig, error) { signers := []ssh.Signer{} for _, keyFilePath := range keyFilePaths { out, err := os.ReadFile(keyFilePath) if err != nil { return nil, fmt.Errorf("read private ssh key: %w", err) } signer, err := ssh.ParsePrivateKey(out) if err != nil { return nil, fmt.Errorf("parse private key: %w", err) } signers = append(signers, signer) } return &ssh.ClientConfig{ User: userName, Auth: []ssh.AuthMethod{ssh.PublicKeys(signers...)}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), }, nil } func parseSSHHost(host string) (string, string, error) { s := strings.SplitN(host, "@", 2) if len(s) != 2 { return "", "", fmt.Errorf("split host: %s", host) } return s[0], s[1], nil } ================================================ FILE: cmd/helper/ssh_server.go ================================================ package helper import ( "encoding/base64" "fmt" "os" "time" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" helperssh "github.com/loft-sh/devpod/pkg/ssh/server" "github.com/loft-sh/devpod/pkg/ssh/server/port" "github.com/loft-sh/devpod/pkg/stdio" "github.com/loft-sh/devpod/pkg/token" "github.com/loft-sh/log" "github.com/loft-sh/ssh" "github.com/pkg/errors" "github.com/spf13/cobra" ) // SSHServerCmd holds the ssh server cmd flags type SSHServerCmd struct { *flags.GlobalFlags Token string Address string Stdio bool TrackActivity bool ReuseSSHAuthSock string Workdir string } // NewSSHServerCmd creates a new ssh command func NewSSHServerCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &SSHServerCmd{ GlobalFlags: flags, } sshCmd := &cobra.Command{ Use: "ssh-server", Short: "Starts a new ssh server", Args: cobra.NoArgs, RunE: cmd.Run, } sshCmd.Flags().StringVar(&cmd.Address, "address", fmt.Sprintf("0.0.0.0:%d", helperssh.DefaultPort), "Address to listen to") sshCmd.Flags().BoolVar(&cmd.Stdio, "stdio", false, "Will listen on stdout and stdin instead of an address") sshCmd.Flags().BoolVar(&cmd.TrackActivity, "track-activity", false, "If enabled will write the last activity time to a file") sshCmd.Flags().StringVar(&cmd.ReuseSSHAuthSock, "reuse-ssh-auth-sock", "", "If set, the SSH_AUTH_SOCK is expected to already be available in the workspace (under /tmp using the key provided) and the connection reuses this instead of creating a new one") _ = sshCmd.Flags().MarkHidden("reuse-ssh-auth-sock") sshCmd.Flags().StringVar(&cmd.Token, "token", "", "Base64 encoded token to use") sshCmd.Flags().StringVar(&cmd.Workdir, "workdir", "", "Directory where commands will run on the host") return sshCmd } // Run runs the command logic func (cmd *SSHServerCmd) Run(_ *cobra.Command, _ []string) error { var ( keys []ssh.PublicKey hostKey []byte err error ) if cmd.Token != "" { // parse token t, err := token.ParseToken(cmd.Token) if err != nil { return errors.Wrap(err, "parse token") } if t.AuthorizedKeys != "" { keyBytes, err := base64.StdEncoding.DecodeString(t.AuthorizedKeys) if err != nil { return fmt.Errorf("seems like the provided encoded string is not base64 encoded") } for len(keyBytes) > 0 { key, _, _, rest, err := ssh.ParseAuthorizedKey(keyBytes) if err != nil { return errors.Wrap(err, "parse authorized key") } keys = append(keys, key) keyBytes = rest } } if len(t.HostKey) > 0 { hostKey, err = base64.StdEncoding.DecodeString(t.HostKey) if err != nil { return fmt.Errorf("decode host key") } } } // start the server server, err := helperssh.NewServer(cmd.Address, hostKey, keys, cmd.Workdir, cmd.ReuseSSHAuthSock, log.Default.ErrorStreamOnly()) if err != nil { return err } // should we listen on stdout & stdin? if cmd.Stdio { if cmd.TrackActivity { go func() { _, err = os.Stat(agent.ContainerActivityFile) if err != nil { err = os.WriteFile(agent.ContainerActivityFile, nil, 0o777) if err != nil { fmt.Fprintf(os.Stderr, "Error writing file: %v\n", err) return } _ = os.Chmod(agent.ContainerActivityFile, 0o777) } for { time.Sleep(time.Second * 10) file, _ := os.Create(agent.ContainerActivityFile) file.Close() } }() } lis := stdio.NewStdioListener(os.Stdin, os.Stdout, true) return server.Serve(lis) } // check if ssh is already running at that port available, err := port.IsAvailable(cmd.Address) if !available { if err != nil { return fmt.Errorf("address %s already in use: %w", cmd.Address, err) } log.Default.ErrorStreamOnly().Infof("address %s already in use", cmd.Address) return nil } return server.ListenAndServe() } ================================================ FILE: cmd/helper/strings/strings.go ================================================ package strings import ( "github.com/loft-sh/devpod/cmd/flags" "github.com/spf13/cobra" ) // NewStringsCmd returns a new command func NewStringsCmd(flags *flags.GlobalFlags) *cobra.Command { stringsCmd := &cobra.Command{ Use: "strings", Short: "DevPod String Utility Commands", Hidden: true, } return stringsCmd } ================================================ FILE: cmd/ide/ide.go ================================================ package ide import ( "github.com/loft-sh/devpod/cmd/flags" "github.com/spf13/cobra" ) // NewIDECmd returns a new command func NewIDECmd(flags *flags.GlobalFlags) *cobra.Command { ideCmd := &cobra.Command{ Use: "ide", Short: "DevPod IDE commands", } ideCmd.AddCommand(NewUseCmd(flags)) ideCmd.AddCommand(NewSetOptionsCmd(flags)) ideCmd.AddCommand(NewOptionsCmd(flags)) ideCmd.AddCommand(NewListCmd(flags)) return ideCmd } ================================================ FILE: cmd/ide/list.go ================================================ package ide import ( "context" "encoding/json" "fmt" "sort" "strconv" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/ide/ideparse" "github.com/loft-sh/log" "github.com/loft-sh/log/table" "github.com/spf13/cobra" ) // ListCmd holds the list cmd flags type ListCmd struct { flags.GlobalFlags Output string } // NewListCmd creates a new command func NewListCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &ListCmd{ GlobalFlags: *flags, } listCmd := &cobra.Command{ Use: "list", Aliases: []string{"ls"}, Short: "List available IDEs", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background()) }, } listCmd.Flags().StringVar(&cmd.Output, "output", "plain", "The output format to use. Can be json or plain") return listCmd } type IDEWithDefault struct { ideparse.AllowedIDE `json:",inline"` Default bool `json:"default,omitempty"` } // Run runs the command logic func (cmd *ListCmd) Run(ctx context.Context) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } if cmd.Output == "plain" { tableEntries := [][]string{} for _, entry := range ideparse.AllowedIDEs { tableEntries = append(tableEntries, []string{ string(entry.Name), strconv.FormatBool(devPodConfig.Current().DefaultIDE == string(entry.Name)), }) } sort.SliceStable(tableEntries, func(i, j int) bool { return tableEntries[i][0] < tableEntries[j][0] }) table.PrintTable(log.Default, []string{ "Name", "Default", }, tableEntries) } else if cmd.Output == "json" { ides := []IDEWithDefault{} for _, entry := range ideparse.AllowedIDEs { ides = append(ides, IDEWithDefault{ AllowedIDE: entry, Default: devPodConfig.Current().DefaultIDE == string(entry.Name), }) } out, err := json.MarshalIndent(ides, "", " ") if err != nil { return err } fmt.Print(string(out)) } else { return fmt.Errorf("unexpected output format, choose either json or plain. Got %s", cmd.Output) } return nil } ================================================ FILE: cmd/ide/options.go ================================================ package ide import ( "context" "encoding/json" "fmt" "sort" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/ide" "github.com/loft-sh/devpod/pkg/ide/ideparse" "github.com/loft-sh/log" "github.com/loft-sh/log/table" "github.com/spf13/cobra" ) // OptionsCmd holds the options cmd flags type OptionsCmd struct { flags.GlobalFlags Output string } // NewOptionsCmd creates a new command func NewOptionsCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &OptionsCmd{ GlobalFlags: *flags, } optionsCmd := &cobra.Command{ Use: "options", Short: "List ide options", RunE: func(_ *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("please specify the ide") } return cmd.Run(context.Background(), args[0]) }, } optionsCmd.Flags().StringVar(&cmd.Output, "output", "plain", "The output format to use. Can be json or plain") return optionsCmd } type optionWithValue struct { ide.Option `json:",inline"` Value string `json:"value,omitempty"` } // Run runs the command logic func (cmd *OptionsCmd) Run(ctx context.Context, ide string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } values := devPodConfig.IDEOptions(ide) ideOptions, err := ideparse.GetIDEOptions(ide) if err != nil { return err } if cmd.Output == "plain" { tableEntries := [][]string{} for optionName, entry := range ideOptions { value := values[optionName].Value tableEntries = append(tableEntries, []string{ optionName, entry.Description, entry.Default, value, }) } sort.SliceStable(tableEntries, func(i, j int) bool { return tableEntries[i][0] < tableEntries[j][0] }) table.PrintTable(log.Default, []string{ "Name", "Description", "Default", "Value", }, tableEntries) } else if cmd.Output == "json" { options := map[string]optionWithValue{} for optionName, entry := range ideOptions { options[optionName] = optionWithValue{ Option: entry, Value: values[optionName].Value, } } out, err := json.Marshal(options) if err != nil { return err } fmt.Print(string(out)) } else { return fmt.Errorf("unexpected output format, choose either json or plain. Got %s", cmd.Output) } return nil } ================================================ FILE: cmd/ide/set_options.go ================================================ package ide import ( "context" "fmt" "strings" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/ide/ideparse" "github.com/pkg/errors" "github.com/spf13/cobra" ) // SetOptionsCmd holds the setOptions cmd flags type SetOptionsCmd struct { flags.GlobalFlags Options []string } // NewSetOptionsCmd creates a new command func NewSetOptionsCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &SetOptionsCmd{ GlobalFlags: *flags, } setOptionsCmd := &cobra.Command{ Use: "set-options", Short: "Configure ide options", RunE: func(_ *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("please specify the ide") } return cmd.Run(context.Background(), args[0]) }, } setOptionsCmd.Flags().StringArrayVarP(&cmd.Options, "option", "o", []string{}, "IDE option in the form KEY=VALUE") return setOptionsCmd } // Run runs the command logic func (cmd *SetOptionsCmd) Run(ctx context.Context, ide string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } ide = strings.ToLower(ide) ideOptions, err := ideparse.GetIDEOptions(ide) if err != nil { return err } // check if there are setOptionsr options set if len(cmd.Options) > 0 { err = setOptions(devPodConfig, ide, cmd.Options, ideOptions) if err != nil { return err } } err = config.SaveConfig(devPodConfig) if err != nil { return errors.Wrap(err, "save config") } return nil } ================================================ FILE: cmd/ide/use.go ================================================ package ide import ( "context" "fmt" "strings" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/ide" "github.com/loft-sh/devpod/pkg/ide/ideparse" "github.com/pkg/errors" "github.com/spf13/cobra" ) // UseCmd holds the use cmd flags type UseCmd struct { flags.GlobalFlags Options []string } // NewUseCmd creates a new command func NewUseCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &UseCmd{ GlobalFlags: *flags, } useCmd := &cobra.Command{ Use: "use", Short: "Configure the default IDE to use (list available IDEs with 'devpod ide list')", Long: `Configure the default IDE to use Available IDEs can be listed with 'devpod ide list'`, RunE: func(_ *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("please specify the ide to use, list available IDEs with 'devpod ide list'") } return cmd.Run(context.Background(), args[0]) }, } useCmd.Flags().StringArrayVarP(&cmd.Options, "option", "o", []string{}, "IDE option in the form KEY=VALUE") return useCmd } // Run runs the command logic func (cmd *UseCmd) Run(ctx context.Context, ide string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } ide = strings.ToLower(ide) ideOptions, err := ideparse.GetIDEOptions(ide) if err != nil { return err } // check if there are user options set if len(cmd.Options) > 0 { err = setOptions(devPodConfig, ide, cmd.Options, ideOptions) if err != nil { return err } } devPodConfig.Current().DefaultIDE = ide err = config.SaveConfig(devPodConfig) if err != nil { return errors.Wrap(err, "save config") } return nil } func setOptions(devPodConfig *config.Config, ide string, options []string, ideOptions ide.Options) error { optionValues, err := ideparse.ParseOptions(options, ideOptions) if err != nil { return err } if devPodConfig.Current().IDEs == nil { devPodConfig.Current().IDEs = map[string]*config.IDEConfig{} } newValues := map[string]config.OptionValue{} if devPodConfig.Current().IDEs[ide] != nil { for k, v := range devPodConfig.Current().IDEs[ide].Options { newValues[k] = v } } for k, v := range optionValues { newValues[k] = v } devPodConfig.Current().IDEs[ide] = &config.IDEConfig{ Options: newValues, } return nil } ================================================ FILE: cmd/import.go ================================================ package cmd import ( "bytes" "context" "encoding/base64" "encoding/json" "fmt" "os" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/extract" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // ImportCmd holds the export cmd flags type ImportCmd struct { *flags.GlobalFlags WorkspaceID string MachineID string MachineReuse bool ProviderID string ProviderReuse bool Data string } // NewImportCmd creates a new command func NewImportCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &ImportCmd{ GlobalFlags: flags, } importCmd := &cobra.Command{ Use: "import", Short: "Imports a workspace configuration", Args: cobra.NoArgs, Hidden: true, RunE: func(_ *cobra.Command, args []string) error { ctx := context.Background() devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } return cmd.Run(ctx, devPodConfig, log.Default) }, } importCmd.Flags().StringVar(&cmd.WorkspaceID, "workspace-id", "", "To workspace id to use") importCmd.Flags().StringVar(&cmd.MachineID, "machine-id", "", "The machine id to use") importCmd.Flags().BoolVar(&cmd.MachineReuse, "machine-reuse", false, "If machine already exists, reuse existing machine") importCmd.Flags().StringVar(&cmd.ProviderID, "provider-id", "", "The provider id to use") importCmd.Flags().BoolVar(&cmd.ProviderReuse, "provider-reuse", false, "If provider already exists, reuse existing provider") importCmd.Flags().StringVar(&cmd.Data, "data", "", "The data to import as raw json") _ = importCmd.MarkFlagRequired("data") return importCmd } // Run runs the command logic func (cmd *ImportCmd) Run(ctx context.Context, devPodConfig *config.Config, log log.Logger) error { exportConfig := &provider.ExportConfig{} err := json.Unmarshal([]byte(cmd.Data), exportConfig) if err != nil { return fmt.Errorf("decode workspace data: %w", err) } else if exportConfig.Workspace == nil { return fmt.Errorf("workspace is missing in imported data") } else if exportConfig.Provider == nil { return fmt.Errorf("provider is missing in imported data") } // set ids correctly if cmd.MachineID == "" && exportConfig.Machine != nil { cmd.MachineID = exportConfig.Machine.ID } if cmd.WorkspaceID == "" { cmd.WorkspaceID = exportConfig.Workspace.ID } if cmd.ProviderID == "" { cmd.ProviderID = exportConfig.Provider.ID } // check if conflicting ids err = cmd.checkForConflictingIDs(ctx, exportConfig, devPodConfig, log) if err != nil { return err } // import provider err = cmd.importProvider(devPodConfig, exportConfig, log) if err != nil { return err } // import machine err = cmd.importMachine(devPodConfig, exportConfig, log) if err != nil { return err } // import workspace err = cmd.importWorkspace(devPodConfig, exportConfig, log) if err != nil { return err } return nil } func (cmd *ImportCmd) importWorkspace(devPodConfig *config.Config, exportConfig *provider.ExportConfig, log log.Logger) error { workspaceDir, err := provider.GetWorkspaceDir(devPodConfig.DefaultContext, cmd.WorkspaceID) if err != nil { return fmt.Errorf("get workspace dir: %w", err) } err = os.MkdirAll(workspaceDir, 0755) if err != nil { return fmt.Errorf("create workspace dir: %w", err) } decoded, err := base64.RawStdEncoding.DecodeString(exportConfig.Workspace.Data) if err != nil { return fmt.Errorf("decode workspace data: %w", err) } err = extract.Extract(bytes.NewReader(decoded), workspaceDir) if err != nil { return fmt.Errorf("extract workspace data: %w", err) } // exchange config workspaceConfig, err := provider.LoadWorkspaceConfig(devPodConfig.DefaultContext, cmd.WorkspaceID) if err != nil { return fmt.Errorf("load machine config: %w", err) } workspaceConfig.ID = cmd.WorkspaceID workspaceConfig.Context = devPodConfig.DefaultContext workspaceConfig.Machine.ID = cmd.MachineID workspaceConfig.Provider.Name = cmd.ProviderID // save machine config err = provider.SaveWorkspaceConfig(workspaceConfig) if err != nil { return fmt.Errorf("save workspace config: %w", err) } log.Donef("Successfully imported workspace %s", cmd.WorkspaceID) return nil } func (cmd *ImportCmd) importMachine(devPodConfig *config.Config, exportConfig *provider.ExportConfig, log log.Logger) error { if exportConfig.Machine == nil { return nil } // if machine already exists we skip if cmd.MachineReuse && provider.MachineExists(devPodConfig.DefaultContext, cmd.MachineID) { log.Infof("Reusing existing machine %s", cmd.MachineID) return nil } machineDir, err := provider.GetMachineDir(devPodConfig.DefaultContext, cmd.MachineID) if err != nil { return fmt.Errorf("get machine dir: %w", err) } err = os.MkdirAll(machineDir, 0755) if err != nil { return fmt.Errorf("create machine dir: %w", err) } decoded, err := base64.RawStdEncoding.DecodeString(exportConfig.Machine.Data) if err != nil { return fmt.Errorf("decode machine data: %w", err) } err = extract.Extract(bytes.NewReader(decoded), machineDir) if err != nil { return fmt.Errorf("extract machine data: %w", err) } // exchange config machineConfig, err := provider.LoadMachineConfig(devPodConfig.DefaultContext, cmd.MachineID) if err != nil { return fmt.Errorf("load machine config: %w", err) } machineConfig.ID = cmd.MachineID machineConfig.Context = devPodConfig.DefaultContext machineConfig.Provider.Name = cmd.ProviderID // save machine config err = provider.SaveMachineConfig(machineConfig) if err != nil { return fmt.Errorf("save machine config: %w", err) } log.Donef("Successfully imported machine %s", cmd.MachineID) return nil } func (cmd *ImportCmd) importProvider(devPodConfig *config.Config, exportConfig *provider.ExportConfig, log log.Logger) error { // if provider already exists we skip if cmd.ProviderReuse && provider.ProviderExists(devPodConfig.DefaultContext, cmd.ProviderID) { log.Infof("Reusing existing provider %s", cmd.ProviderID) return nil } providerDir, err := provider.GetProviderDir(devPodConfig.DefaultContext, cmd.ProviderID) if err != nil { return fmt.Errorf("get provider dir: %w", err) } err = os.MkdirAll(providerDir, 0755) if err != nil { return fmt.Errorf("create provider dir: %w", err) } decoded, err := base64.RawStdEncoding.DecodeString(exportConfig.Provider.Data) if err != nil { return fmt.Errorf("decode provider data: %w", err) } err = extract.Extract(bytes.NewReader(decoded), providerDir) if err != nil { return fmt.Errorf("extract provider data: %w", err) } // exchange config providerConfig, err := provider.LoadProviderConfig(devPodConfig.DefaultContext, cmd.ProviderID) if err != nil { return fmt.Errorf("load provider config: %w", err) } providerConfig.Name = cmd.ProviderID // save provider config err = provider.SaveProviderConfig(devPodConfig.DefaultContext, providerConfig) if err != nil { return fmt.Errorf("save provider config: %w", err) } // add provider options if exportConfig.Provider.Config != nil { if devPodConfig.Current().Providers == nil { devPodConfig.Current().Providers = map[string]*config.ProviderConfig{} } devPodConfig.Current().Providers[cmd.ProviderID] = exportConfig.Provider.Config err = config.SaveConfig(devPodConfig) if err != nil { return fmt.Errorf("save devpod config: %w", err) } } log.Donef("Successfully imported provider %s", cmd.ProviderID) return nil } func (cmd *ImportCmd) checkForConflictingIDs(ctx context.Context, exportConfig *provider.ExportConfig, devPodConfig *config.Config, log log.Logger) error { workspaces, err := workspace.List(ctx, devPodConfig, false, cmd.Owner, log) if err != nil { return fmt.Errorf("error listing workspaces: %w", err) } // check for workspace duplicate if exportConfig.Workspace != nil { for _, workspace := range workspaces { if workspace.ID == cmd.WorkspaceID { return fmt.Errorf("existing workspace with id %s found, please use --workspace-id to override the workspace id", cmd.WorkspaceID) } else if workspace.UID == exportConfig.Workspace.UID { return fmt.Errorf("existing workspace %s with uid %s found, please use --workspace-id to override the workspace id", workspace.ID, workspace.UID) } } } // check if machine already exists if !cmd.MachineReuse && exportConfig.Machine != nil { if provider.MachineExists(devPodConfig.DefaultContext, cmd.MachineID) { return fmt.Errorf("existing machine with id %s found, please use --machine-reuse to skip importing the machine or --machine-id to override the machine id", cmd.MachineID) } } // check if provider already exists if !cmd.ProviderReuse && exportConfig.Provider != nil { if provider.ProviderExists(devPodConfig.DefaultContext, cmd.ProviderID) { return fmt.Errorf("existing provider with id %s found, please use --provider-reuse to skip importing the provider or --provider-id to override the provider id", cmd.ProviderID) } } return nil } ================================================ FILE: cmd/list.go ================================================ package cmd import ( "context" "encoding/json" "fmt" "sort" "time" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/loft-sh/log/table" "github.com/spf13/cobra" ) // ListCmd holds the configuration type ListCmd struct { *flags.GlobalFlags Output string SkipPro bool } // NewListCmd creates a new destroy command func NewListCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &ListCmd{ GlobalFlags: flags, } listCmd := &cobra.Command{ Use: "list", Aliases: []string{"ls"}, Short: "Lists existing workspaces", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, args []string) error { if len(args) > 0 { return fmt.Errorf("no arguments are allowed for this command") } return cmd.Run(context.Background()) }, } listCmd.Flags().StringVar(&cmd.Output, "output", "plain", "The output format to use. Can be json or plain") listCmd.Flags().BoolVar(&cmd.SkipPro, "skip-pro", false, "Don't list pro workspaces") return listCmd } // Run runs the command logic func (cmd *ListCmd) Run(ctx context.Context) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } workspaces, err := workspace.List(ctx, devPodConfig, cmd.SkipPro, cmd.Owner, log.Default) if err != nil { return err } if cmd.Output == "json" { sort.SliceStable(workspaces, func(i, j int) bool { return workspaces[i].LastUsedTimestamp.Time.Unix() > workspaces[j].LastUsedTimestamp.Time.Unix() }) out, err := json.Marshal(workspaces) if err != nil { return err } fmt.Print(string(out)) } else if cmd.Output == "plain" { tableEntries := [][]string{} sort.SliceStable(workspaces, func(i, j int) bool { return workspaces[i].LastUsedTimestamp.Time.Unix() > workspaces[j].LastUsedTimestamp.Time.Unix() }) for _, entry := range workspaces { name := entry.ID if entry.IsPro() && entry.Pro.DisplayName != "" && entry.ID != entry.Pro.DisplayName { name = fmt.Sprintf("%s (%s)", entry.Pro.DisplayName, entry.ID) } tableEntries = append(tableEntries, []string{ name, entry.Source.String(), entry.Machine.ID, entry.Provider.Name, entry.IDE.Name, time.Since(entry.LastUsedTimestamp.Time).Round(1 * time.Second).String(), time.Since(entry.CreationTimestamp.Time).Round(1 * time.Second).String(), fmt.Sprintf("%t", entry.IsPro()), }) } table.PrintTable(log.Default, []string{ "Name", "Source", "Machine", "Provider", "IDE", "Last Used", "Age", "Pro", }, tableEntries) } else { return fmt.Errorf("unexpected output format, choose either json or plain. Got %s", cmd.Output) } return nil } ================================================ FILE: cmd/logs.go ================================================ package cmd import ( "context" "fmt" "io" "os" "github.com/loft-sh/devpod/cmd/completion" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" clientpkg "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/ssh" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // LogsCmd holds the configuration type LogsCmd struct { *flags.GlobalFlags } // NewLogsCmd creates a new destroy command func NewLogsCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &LogsCmd{ GlobalFlags: flags, } startCmd := &cobra.Command{ Use: "logs [flags] [workspace-path|workspace-name]", Short: "Prints the workspace logs on the machine", RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), args) }, ValidArgsFunction: func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetWorkspaceSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, log.Default) }, } return startCmd } // Run runs the command logic func (cmd *LogsCmd) Run(ctx context.Context, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } baseClient, err := workspace.Get(ctx, devPodConfig, args, false, cmd.Owner, false, log.Default) if err != nil { return err } client, ok := baseClient.(clientpkg.WorkspaceClient) if !ok { return fmt.Errorf("this command is not supported for proxy providers") } log := log.Default // create readers stdoutReader, stdoutWriter, err := os.Pipe() if err != nil { return err } stdinReader, stdinWriter, err := os.Pipe() if err != nil { return err } defer stdoutWriter.Close() defer stdinWriter.Close() // ssh tunnel command sshServerCmd := fmt.Sprintf("'%s' helper ssh-server --stdio", client.AgentPath()) if log.GetLevel() == logrus.DebugLevel { sshServerCmd += " --debug" } // Get the timeout from the context options timeout := config.ParseTimeOption(devPodConfig, config.ContextOptionAgentInjectTimeout) // start ssh server in background errChan := make(chan error, 1) go func() { stderr := log.ErrorStreamOnly().Writer(logrus.DebugLevel, false) defer stderr.Close() errChan <- agent.InjectAgentAndExecute( ctx, func(ctx context.Context, command string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { return client.Command(ctx, clientpkg.CommandOptions{ Command: command, Stdin: stdin, Stdout: stdout, Stderr: stderr, }) }, client.AgentLocal(), client.AgentPath(), client.AgentURL(), true, sshServerCmd, stdinReader, stdoutWriter, stderr, log.ErrorStreamOnly(), timeout) }() // create agent command agentCommand := fmt.Sprintf("'%s' agent workspace logs --context '%s' --id '%s'", client.AgentPath(), client.Context(), client.Workspace()) if log.GetLevel() == logrus.DebugLevel { agentCommand += " --debug" } // create new ssh client // start ssh client as root / default user sshClient, err := ssh.StdioClientWithUser(stdoutReader, stdinWriter, "" /* default */, false) if err != nil { return err } defer sshClient.Close() session, err := sshClient.NewSession() if err != nil { return err } defer session.Close() session.Stdout = os.Stdout session.Stderr = os.Stderr err = session.Run(agentCommand) if err != nil { return err } return nil } ================================================ FILE: cmd/logs_daemon.go ================================================ package cmd import ( "context" "fmt" "os" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/config" provider2 "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // LogsDaemonCmd holds the configuration type LogsDaemonCmd struct { *flags.GlobalFlags } // NewLogsDaemonCmd creates a new destroy command func NewLogsDaemonCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &LogsDaemonCmd{ GlobalFlags: flags, } startCmd := &cobra.Command{ Use: "logs-daemon", Short: "Prints the daemon logs on the machine", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } return startCmd } // Run runs the command logic func (cmd *LogsDaemonCmd) Run(ctx context.Context, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } baseClient, err := workspace.Get(ctx, devPodConfig, args, false, cmd.Owner, false, log.Default) if err != nil { return err } else if baseClient.WorkspaceConfig().Machine.ID == "" { return fmt.Errorf("selected workspace is not a machine provider, there is not daemon running") } workspaceClient, ok := baseClient.(client.WorkspaceClient) if !ok { return fmt.Errorf("this command is not supported for proxy providers") } _, agentInfo, err := workspaceClient.AgentInfo(provider2.CLIOptions{}) if err != nil { return err } command := fmt.Sprintf("'%s' agent workspace logs-daemon --context '%s' --id '%s'", workspaceClient.AgentPath(), workspaceClient.Context(), workspaceClient.Workspace()) if agentInfo.Agent.DataPath != "" { command += fmt.Sprintf(" --agent-dir '%s'", agentInfo.Agent.DataPath) } // read daemon logs return workspaceClient.Command(ctx, client.CommandOptions{ Command: command, Stdout: os.Stdout, Stderr: os.Stderr, }) } ================================================ FILE: cmd/machine/create.go ================================================ package machine import ( "context" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // CreateCmd holds the configuration type CreateCmd struct { *flags.GlobalFlags ProviderOptions []string } // NewCreateCmd creates a new create command func NewCreateCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &CreateCmd{ GlobalFlags: flags, } createCmd := &cobra.Command{ Use: "create [name]", Short: "Creates a new machine", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } createCmd.Flags().StringSliceVar(&cmd.ProviderOptions, "provider-option", []string{}, "Provider option in the form KEY=VALUE") return createCmd } // Run runs the command logic func (cmd *CreateCmd) Run(ctx context.Context, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } machineClient, err := workspace.ResolveMachine(devPodConfig, args, cmd.ProviderOptions, log.Default) if err != nil { return err } err = machineClient.Create(ctx, client.CreateOptions{}) if err != nil { return err } return nil } ================================================ FILE: cmd/machine/delete.go ================================================ package machine import ( "context" "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // DeleteCmd holds the configuration type DeleteCmd struct { *flags.GlobalFlags GracePeriod string Force bool } // NewDeleteCmd creates a new destroy command func NewDeleteCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &DeleteCmd{ GlobalFlags: flags, } deleteCmd := &cobra.Command{ Use: "delete [name]", Short: "Deletes an existing machine", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } deleteCmd.Flags().StringVar(&cmd.GracePeriod, "grace-period", "", "The amount of time to give the command to delete the workspace") deleteCmd.Flags().BoolVar(&cmd.Force, "force", false, "Delete workspace even if it is not found remotely anymore") return deleteCmd } // Run runs the command logic func (cmd *DeleteCmd) Run(ctx context.Context, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } machineClient, err := workspace.GetMachine(devPodConfig, args, log.Default) if err != nil { return err } // check if there are workspaces that still use this machine workspaces, err := workspace.List(ctx, devPodConfig, false, platform.SelfOwnerFilter, log.Default) if err != nil { return err } // search for workspace that uses this machine for _, workspace := range workspaces { if workspace.Machine.ID == machineClient.Machine() { return fmt.Errorf("cannot delete machine '%s', because workspace '%s' is still using it. Please delete the workspace '%s' before deleting the machine", workspace.Machine.ID, workspace.ID, workspace.ID) } } err = machineClient.Delete(ctx, client.DeleteOptions{ Force: cmd.Force, GracePeriod: cmd.GracePeriod, }) if err != nil { return err } return nil } ================================================ FILE: cmd/machine/inspect.go ================================================ package machine import ( "context" "encoding/json" "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) type InspectCmd struct { *flags.GlobalFlags } func NewInspectCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &InspectCmd{ GlobalFlags: flags, } stopCmd := &cobra.Command{ Use: "inspect", Short: "Inspects an existing machine", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } return stopCmd } func (cmd *InspectCmd) Run(ctx context.Context, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } machineClient, err := workspace.GetMachine(devPodConfig, args, log.Default) if err != nil { return err } p, err := provider.LoadProviderConfig(devPodConfig.DefaultContext, machineClient.Provider()) if err != nil { return err } machineConfig := machineClient.MachineConfig() for k := range machineConfig.Provider.Options { optConfig := p.Options[k] if optConfig.Hidden { delete(machineConfig.Provider.Options, k) continue } if optConfig.Password { opt := machineConfig.Provider.Options[k] opt.Value = "********" } } out, err := json.MarshalIndent(machineConfig, "", " ") if err != nil { return err } fmt.Println(string(out)) return nil } ================================================ FILE: cmd/machine/list.go ================================================ package machine import ( "context" "encoding/json" "fmt" "os" "sort" "time" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/loft-sh/log/table" "github.com/pkg/errors" "github.com/spf13/cobra" ) // ListCmd holds the configuration type ListCmd struct { *flags.GlobalFlags Output string } // NewListCmd creates a new destroy command func NewListCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &ListCmd{ GlobalFlags: flags, } listCmd := &cobra.Command{ Use: "list", Aliases: []string{"ls"}, Short: "Lists existing machines", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background()) }, } listCmd.Flags().StringVar(&cmd.Output, "output", "plain", "The output format to use. Can be json or plain") return listCmd } // Run runs the command logic func (cmd *ListCmd) Run(ctx context.Context) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } machineDir, err := provider.GetMachinesDir(devPodConfig.DefaultContext) if err != nil { return err } entries, err := os.ReadDir(machineDir) if err != nil && !os.IsNotExist(err) { return err } if cmd.Output == "plain" { tableEntries := [][]string{} for _, entry := range entries { machineConfig, err := provider.LoadMachineConfig(devPodConfig.DefaultContext, entry.Name()) if err != nil { return errors.Wrap(err, "load machine config") } tableEntries = append(tableEntries, []string{ machineConfig.ID, machineConfig.Provider.Name, time.Since(machineConfig.CreationTimestamp.Time).Round(1 * time.Second).String(), }) } sort.SliceStable(tableEntries, func(i, j int) bool { return tableEntries[i][0] < tableEntries[j][0] }) table.PrintTable(log.Default, []string{ "Name", "Provider", "Age", }, tableEntries) } else if cmd.Output == "json" { tableEntries := []*provider.Machine{} for _, entry := range entries { machineConfig, err := provider.LoadMachineConfig(devPodConfig.DefaultContext, entry.Name()) if err != nil { return errors.Wrap(err, "load machine config") } tableEntries = append(tableEntries, machineConfig) } sort.SliceStable(tableEntries, func(i, j int) bool { return tableEntries[i].ID < tableEntries[j].ID }) out, err := json.Marshal(tableEntries) if err != nil { return err } fmt.Print(string(out)) } else { return fmt.Errorf("unexpected output format, choose either json or plain. Got %s", cmd.Output) } return nil } ================================================ FILE: cmd/machine/machine.go ================================================ package machine import ( "github.com/loft-sh/devpod/cmd/flags" "github.com/spf13/cobra" ) // NewMachineCmd returns a new root command func NewMachineCmd(flags *flags.GlobalFlags) *cobra.Command { machineCmd := &cobra.Command{ Use: "machine", Short: "DevPod Machine commands", } machineCmd.AddCommand(NewListCmd(flags)) machineCmd.AddCommand(NewSSHCmd(flags)) machineCmd.AddCommand(NewStopCmd(flags)) machineCmd.AddCommand(NewStartCmd(flags)) machineCmd.AddCommand(NewStatusCmd(flags)) machineCmd.AddCommand(NewDeleteCmd(flags)) machineCmd.AddCommand(NewCreateCmd(flags)) machineCmd.AddCommand(NewInspectCmd(flags)) return machineCmd } ================================================ FILE: cmd/machine/ssh.go ================================================ package machine import ( "context" "fmt" "io" "os" "github.com/loft-sh/devpod/cmd/flags" devagent "github.com/loft-sh/devpod/pkg/agent" "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/config" devssh "github.com/loft-sh/devpod/pkg/ssh" devsshagent "github.com/loft-sh/devpod/pkg/ssh/agent" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/mattn/go-isatty" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" "golang.org/x/term" ) // SSHCmd holds the configuration type SSHCmd struct { *flags.GlobalFlags Command string AgentForwarding bool } // NewSSHCmd creates a new destroy command func NewSSHCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &SSHCmd{ GlobalFlags: flags, } sshCmd := &cobra.Command{ Use: "ssh [name]", Short: "SSH into the machine", RunE: func(c *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } sshCmd.Flags().StringVar(&cmd.Command, "command", "", "The command to execute on the remote machine") sshCmd.Flags().BoolVar(&cmd.AgentForwarding, "agent-forwarding", false, "If true, will forward the local ssh keys") return sshCmd } // Run runs the command logic func (cmd *SSHCmd) Run(ctx context.Context, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } machineClient, err := workspace.GetMachine(devPodConfig, args, log.Default) if err != nil { return err } writer := log.Default.ErrorStreamOnly().Writer(logrus.InfoLevel, false) defer writer.Close() // Get the timeout from the context options timeout := config.ParseTimeOption(devPodConfig, config.ContextOptionAgentInjectTimeout) // start the ssh session return StartSSHSession( ctx, "", cmd.Command, cmd.AgentForwarding, func(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { command := fmt.Sprintf("'%s' helper ssh-server --stdio", machineClient.AgentPath()) if cmd.Debug { command += " --debug" } return devagent.InjectAgentAndExecute(ctx, func(ctx context.Context, command string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { return machineClient.Command(ctx, client.CommandOptions{ Command: command, Stdin: stdin, Stdout: stdout, Stderr: stderr, }) }, machineClient.AgentLocal(), machineClient.AgentPath(), machineClient.AgentURL(), true, command, stdin, stdout, stderr, log.Default.ErrorStreamOnly(), timeout) }, writer) } type ExecFunc func(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error func StartSSHSession(ctx context.Context, user, command string, agentForwarding bool, exec ExecFunc, stderr io.Writer) error { // create readers stdoutReader, stdoutWriter, err := os.Pipe() if err != nil { return err } defer stdoutReader.Close() defer stdoutWriter.Close() stdinReader, stdinWriter, err := os.Pipe() if err != nil { return err } defer stdinWriter.Close() defer stdinReader.Close() // start ssh machine errChan := make(chan error, 1) go func() { errChan <- exec(ctx, stdinReader, stdoutWriter, stderr) }() sshClient, err := devssh.StdioClientWithUser(stdoutReader, stdinWriter, user, false) if err != nil { return err } defer sshClient.Close() return RunSSHSession(ctx, sshClient, agentForwarding, command, stderr) } func RunSSHSession(ctx context.Context, sshClient *ssh.Client, agentForwarding bool, command string, stderr io.Writer) error { // create a new session session, err := sshClient.NewSession() if err != nil { return err } defer session.Close() // request agent forwarding authSock := devsshagent.GetSSHAuthSocket() if agentForwarding && authSock != "" { err = devsshagent.ForwardToRemote(sshClient, authSock) if err != nil { return errors.Errorf("forward agent: %v", err) } err = devsshagent.RequestAgentForwarding(session) if err != nil { return errors.Errorf("request agent forwarding: %v", err) } } stdout := os.Stdout stdin := os.Stdin if isatty.IsTerminal(stdout.Fd()) { state, err := term.MakeRaw(int(stdout.Fd())) if err != nil { return err } defer func() { _ = term.Restore(int(stdout.Fd()), state) }() windowChange := devssh.WatchWindowSize(ctx) go func() { for { select { case <-ctx.Done(): return case <-windowChange: } width, height, err := term.GetSize(int(stdout.Fd())) if err != nil { continue } _ = session.WindowChange(height, width) } }() // get initial terminal t := "xterm-256color" termEnv, ok := os.LookupEnv("TERM") if ok { t = termEnv } // get initial window size width, height := 80, 40 if w, h, err := term.GetSize(int(stdout.Fd())); err == nil { width, height = w, h } if err = session.RequestPty(t, height, width, ssh.TerminalModes{}); err != nil { return fmt.Errorf("request pty: %w", err) } } session.Stdin = stdin session.Stdout = stdout session.Stderr = stderr if command == "" { if err := session.Shell(); err != nil { return fmt.Errorf("start ssh session with shell: %w", err) } } else { if err := session.Start(command); err != nil { return fmt.Errorf("start ssh session with command %s: %w", command, err) } } if err := session.Wait(); err != nil { return fmt.Errorf("ssh session: %w", err) } return nil } ================================================ FILE: cmd/machine/start.go ================================================ package machine import ( "context" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // StartCmd holds the configuration type StartCmd struct { *flags.GlobalFlags } // NewStartCmd creates a new destroy command func NewStartCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &StartCmd{ GlobalFlags: flags, } startCmd := &cobra.Command{ Use: "start [name]", Short: "Starts an existing machine", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } return startCmd } // Run runs the command logic func (cmd *StartCmd) Run(ctx context.Context, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } machineClient, err := workspace.GetMachine(devPodConfig, args, log.Default) if err != nil { return err } err = machineClient.Start(ctx, client.StartOptions{}) if err != nil { return err } return nil } ================================================ FILE: cmd/machine/status.go ================================================ package machine import ( "context" "encoding/json" "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // StatusCmd holds the configuration type StatusCmd struct { *flags.GlobalFlags Output string } // NewStatusCmd creates a new destroy command func NewStatusCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &StatusCmd{ GlobalFlags: flags, } statusCmd := &cobra.Command{ Use: "status [name]", Short: "Retrieves the status of an existing machine", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } statusCmd.Flags().StringVar(&cmd.Output, "output", "plain", "Status shows the machine status") return statusCmd } // Run runs the command logic func (cmd *StatusCmd) Run(ctx context.Context, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } machineClient, err := workspace.GetMachine(devPodConfig, args, log.Default) if err != nil { return err } // get status machineStatus, err := machineClient.Status(ctx, client.StatusOptions{}) if err != nil { return err } if cmd.Output == "plain" { if machineStatus == client.StatusStopped { log.Default.Infof("Machine '%s' is '%s', you can start it via 'devpod machine start %s'", machineClient.Machine(), machineStatus, machineClient.Machine()) } else if machineStatus == client.StatusBusy { log.Default.Infof("Machine '%s' is '%s', which means its currently unaccessible. This is usually resolved by waiting a couple of minutes", machineClient.Machine(), machineStatus) } else if machineStatus == client.StatusNotFound { log.Default.Infof("Machine '%s' is '%s'", machineClient.Machine(), machineStatus) } else { log.Default.Infof("Machine '%s' is '%s'", machineClient.Machine(), machineStatus) } } else if cmd.Output == "json" { out, err := json.Marshal(struct { ID string `json:"id,omitempty"` Context string `json:"context,omitempty"` Provider string `json:"provider,omitempty"` State string `json:"state,omitempty"` }{ ID: machineClient.Machine(), Context: machineClient.Context(), Provider: machineClient.Provider(), State: string(machineStatus), }) if err != nil { return err } fmt.Print(string(out)) } else { return fmt.Errorf("unexpected output format, choose either json or plain. Got %s", cmd.Output) } return nil } ================================================ FILE: cmd/machine/stop.go ================================================ package machine import ( "context" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // StopCmd holds the configuration type StopCmd struct { *flags.GlobalFlags } // NewStopCmd creates a new destroy command func NewStopCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &StopCmd{ GlobalFlags: flags, } stopCmd := &cobra.Command{ Use: "stop [name]", Short: "Stops an existing machine", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } return stopCmd } // Run runs the command logic func (cmd *StopCmd) Run(ctx context.Context, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } machineClient, err := workspace.GetMachine(devPodConfig, args, log.Default) if err != nil { return err } err = machineClient.Stop(ctx, client.StopOptions{}) if err != nil { return err } return nil } ================================================ FILE: cmd/ping.go ================================================ package cmd import ( "context" "fmt" "os" "github.com/loft-sh/devpod/cmd/completion" "github.com/loft-sh/devpod/cmd/flags" client2 "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/config" workspace2 "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) type PingCmd struct { *flags.GlobalFlags } func NewPingCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &PingCmd{ GlobalFlags: flags, } troubleshootCmd := &cobra.Command{ Use: "ping [workspace-path|workspace-name]", Short: "Pings the DevPod Pro workspace", RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), args) }, ValidArgsFunction: func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetWorkspaceSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, log.Default) }, Hidden: true, } return troubleshootCmd } func (cmd *PingCmd) Run(ctx context.Context, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } client, err := workspace2.Get(ctx, devPodConfig, args, true, cmd.Owner, false, log.Default.ErrorStreamOnly()) if err != nil { return err } daemonClient, ok := client.(client2.DaemonClient) if !ok { return fmt.Errorf("ping is only available for pro workspaces") } return daemonClient.Ping(ctx, os.Stdout) } ================================================ FILE: cmd/pro/add/add.go ================================================ package add import ( proflags "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/spf13/cobra" ) // NewAddCmd creates a new command func NewAddCmd(globalFlags *proflags.GlobalFlags) *cobra.Command { addCmd := &cobra.Command{ Use: "add", Short: "Adds a given resource to DevPod Pro", Args: cobra.NoArgs, } addCmd.AddCommand(NewClusterCmd(globalFlags)) return addCmd } ================================================ FILE: cmd/pro/add/cluster.go ================================================ package add import ( "cmp" "context" "errors" "fmt" "os" "os/exec" "time" "github.com/loft-sh/log" "github.com/loft-sh/log/survey" "github.com/sirupsen/logrus" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/wait" managementv1 "github.com/loft-sh/api/v4/pkg/apis/management/v1" storagev1 "github.com/loft-sh/api/v4/pkg/apis/storage/v1" proflags "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/workspace" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) type ClusterCmd struct { Log log.Logger *proflags.GlobalFlags Namespace string ServiceAccount string DisplayName string KubeContext string Insecure bool Wait bool HelmChartPath string HelmChartVersion string HelmSet []string HelmValues []string Host string } // NewClusterCmd creates a new command func NewClusterCmd(globalFlags *proflags.GlobalFlags) *cobra.Command { cmd := &ClusterCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "cluster ", Short: "add current cluster to DevPod Pro", Args: cobra.ExactArgs(1), RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), args) }, } c.Flags().StringVar(&cmd.Namespace, "namespace", "loft", "The namespace to generate the service account in. The namespace will be created if it does not exist") c.Flags().StringVar(&cmd.ServiceAccount, "service-account", "loft-admin", "The service account name to create") c.Flags().StringVar(&cmd.DisplayName, "display-name", "", "The display name to show in the UI for this cluster") c.Flags().BoolVar(&cmd.Wait, "wait", false, "If true, will wait until the cluster is initialized") c.Flags().BoolVar(&cmd.Insecure, "insecure", false, "If true, deploys the agent in insecure mode") c.Flags().StringVar(&cmd.HelmChartVersion, "helm-chart-version", "", "The agent chart version to deploy") c.Flags().StringVar(&cmd.HelmChartPath, "helm-chart-path", "", "The agent chart to deploy") c.Flags().StringArrayVar(&cmd.HelmSet, "helm-set", []string{}, "Extra helm values for the agent chart") c.Flags().StringArrayVar(&cmd.HelmValues, "helm-values", []string{}, "Extra helm values for the agent chart") c.Flags().StringVar(&cmd.KubeContext, "kube-context", "", "The kube context to use for installation") c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") return c } func (cmd *ClusterCmd) Run(ctx context.Context, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, "") if err != nil { return err } cmd.Host, err = ensureHost(devPodConfig, cmd.Host, cmd.Log) if err != nil { return err } // Get clusterName from command argument clusterName := args[0] baseClient, err := platform.InitClientFromHost(ctx, devPodConfig, cmd.Host, cmd.Log) if err != nil { return err } managementClient, err := baseClient.Management() if err != nil { return err } loftVersion, err := baseClient.Version() if err != nil { return fmt.Errorf("get pro version: %w", err) } user, team := getUserOrTeam(ctx, baseClient) _, err = managementClient.Loft().ManagementV1().Clusters().Create(ctx, &managementv1.Cluster{ ObjectMeta: metav1.ObjectMeta{ Name: clusterName, }, Spec: managementv1.ClusterSpec{ ClusterSpec: storagev1.ClusterSpec{ DisplayName: cmd.DisplayName, Owner: &storagev1.UserOrTeam{ User: user, Team: team, }, NetworkPeer: true, Access: getAccess(user, team), }, }, }, metav1.CreateOptions{}) if err != nil && !kerrors.IsAlreadyExists(err) { return fmt.Errorf("create cluster: %w", err) } accessKey, err := managementClient.Loft().ManagementV1().Clusters().GetAccessKey(ctx, clusterName, metav1.GetOptions{}) if err != nil { return fmt.Errorf("get cluster access key: %w", err) } namespace := cmd.Namespace helmArgs := []string{ "upgrade", "loft", } if os.Getenv("DEVELOPMENT") == "true" { helmArgs = []string{ "upgrade", "--install", "loft", cmp.Or(os.Getenv("DEVELOPMENT_CHART_DIR"), "./chart"), "--create-namespace", "--namespace", namespace, "--set", "agentOnly=true", "--set", "image=" + cmp.Or(os.Getenv("DEVELOPMENT_IMAGE"), "ghcr.io/loft-sh/enterprise:release-test"), } } else { if cmd.HelmChartPath != "" { helmArgs = append(helmArgs, cmd.HelmChartPath) } else { helmArgs = append(helmArgs, "loft", "--repo", "https://charts.loft.sh") } if loftVersion.Version != "" { helmArgs = append(helmArgs, "--version", loftVersion.Version) } if cmd.HelmChartVersion != "" { helmArgs = append(helmArgs, "--version", cmd.HelmChartVersion) } // general arguments helmArgs = append(helmArgs, "--install", "--create-namespace", "--namespace", cmd.Namespace, "--set", "agentOnly=true") } for _, set := range cmd.HelmSet { helmArgs = append(helmArgs, "--set", set) } for _, values := range cmd.HelmValues { helmArgs = append(helmArgs, "--values", values) } if accessKey.LoftHost != "" { helmArgs = append(helmArgs, "--set", "url="+accessKey.LoftHost) } if accessKey.AccessKey != "" { helmArgs = append(helmArgs, "--set", "token="+accessKey.AccessKey) } if cmd.Insecure || accessKey.Insecure { helmArgs = append(helmArgs, "--set", "insecureSkipVerify=true") } if accessKey.CaCert != "" { helmArgs = append(helmArgs, "--set", "additionalCA="+accessKey.CaCert) } if cmd.Wait { helmArgs = append(helmArgs, "--wait") } if cmd.KubeContext != "" { helmArgs = append(helmArgs, "--kube-context", cmd.KubeContext) } kubeClientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{}) if cmd.KubeContext != "" { kubeConfig, err := kubeClientConfig.RawConfig() if err != nil { return fmt.Errorf("there is an error loading your current kube config (%w), please make sure you have access to a kubernetes cluster and the command `kubectl get namespaces` is working", err) } kubeClientConfig = clientcmd.NewNonInteractiveClientConfig(kubeConfig, cmd.KubeContext, &clientcmd.ConfigOverrides{}, clientcmd.NewDefaultClientConfigLoadingRules()) } config, err := kubeClientConfig.ClientConfig() if err != nil { return fmt.Errorf("there is an error loading your current kube config (%w), please make sure you have access to a kubernetes cluster and the command `kubectl get namespaces` is working", err) } clientset, err := kubernetes.NewForConfig(config) if err != nil { return fmt.Errorf("create kube client: %w", err) } errChan := make(chan error) go func() { helmCmd := exec.CommandContext(ctx, "helm", helmArgs...) helmCmd.Stdout = cmd.Log.Writer(logrus.DebugLevel, true) helmCmd.Stderr = cmd.Log.Writer(logrus.DebugLevel, true) helmCmd.Stdin = os.Stdin cmd.Log.Info("Installing agent...") cmd.Log.Debugf("Running helm command: %v", helmCmd.Args) err = helmCmd.Run() if err != nil { errChan <- fmt.Errorf("failed to install chart: %w", err) } close(errChan) }() _, err = platform.WaitForPodReady(ctx, clientset, namespace, cmd.Log) if err = errors.Join(err, <-errChan); err != nil { return fmt.Errorf("wait for pod: %w", err) } if cmd.Wait { cmd.Log.Info("Waiting for the cluster to be initialized...") waitErr := wait.PollUntilContextTimeout(ctx, time.Second, 5*time.Minute, false, func(ctx context.Context) (done bool, err error) { clusterInstance, err := managementClient.Loft().ManagementV1().Clusters().Get(ctx, clusterName, metav1.GetOptions{}) if err != nil && !kerrors.IsNotFound(err) { return false, err } return clusterInstance != nil && clusterInstance.Status.Phase == storagev1.ClusterStatusPhaseInitialized, nil }) if waitErr != nil { return fmt.Errorf("get cluster: %w", waitErr) } } cmd.Log.Donef("Successfully added cluster %s", clusterName) return nil } func ensureHost(devPodConfig *config.Config, host string, log log.Logger) (string, error) { if host != "" { return host, nil } proInstances, err := workspace.ListProInstances(devPodConfig, log) if err != nil { return "", fmt.Errorf("list pro instances: %w", err) } options := []string{} for _, pro := range proInstances { options = append(options, pro.Host) } h, err := log.Question(&survey.QuestionOptions{ Question: "Select Pro instance to connect your cluster to", Options: options, DefaultValue: options[0], }) if err != nil { return "", fmt.Errorf("select pro instance: %w", err) } return h, nil } func getUserOrTeam(ctx context.Context, baseClient client.Client) (string, string) { var user, team string self := baseClient.Self() userName := self.Status.User teamName := self.Status.Team if userName != nil { user = userName.Name } else { team = teamName.Name } return user, team } func getAccess(user, team string) []storagev1.Access { access := []storagev1.Access{ { Verbs: []string{"*"}, Subresources: []string{"*"}, }, } if team != "" { access[0].Teams = []string{team} } else { access[0].Users = []string{user} } return access } ================================================ FILE: cmd/pro/check_health.go ================================================ package pro import ( "bytes" "context" "fmt" "github.com/loft-sh/devpod/cmd/agent" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // CheckHealthCmd holds the cmd flags type CheckHealthCmd struct { *flags.GlobalFlags Log log.Logger Host string } // NewCheckHealthCmd creates a new command func NewCheckHealthCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &CheckHealthCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "check-health", Short: "Check platform health", Hidden: true, RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, PersistentPreRun: func(cmd *cobra.Command, args []string) { root := cmd.Root() if root == nil { return } if root.Annotations == nil { root.Annotations = map[string]string{} } // Don't print debug message root.Annotations[agent.AgentExecutedAnnotation] = "true" }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") return c } func (cmd *CheckHealthCmd) Run(ctx context.Context, devPodConfig *config.Config, provider *provider.ProviderConfig) error { var buf bytes.Buffer // ignore --debug because we tunnel json through stdio cmd.Log.SetLevel(logrus.InfoLevel) err := clientimplementation.RunCommandWithBinaries( ctx, "health", provider.Exec.Proxy.Health, devPodConfig.DefaultContext, nil, nil, devPodConfig.ProviderOptions(provider.Name), provider, nil, nil, &buf, cmd.Log.Writer(logrus.ErrorLevel, true), cmd.Log) if err != nil { return fmt.Errorf("check health with provider \"%s\": %w", provider.Name, err) } fmt.Println(buf.String()) return nil } ================================================ FILE: cmd/pro/check_update.go ================================================ package pro import ( "context" "encoding/json" "fmt" "github.com/loft-sh/devpod/cmd/agent" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/provider" versionpkg "github.com/loft-sh/devpod/pkg/version" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // CheckUpdateCmd holds the cmd flags type CheckUpdateCmd struct { *flags.GlobalFlags Log log.Logger Host string } // NewCheckUpdateCmd creates a new command func NewCheckUpdateCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &CheckUpdateCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "check-update", Short: "Check platform provider update", Hidden: true, RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, PersistentPreRun: func(cmd *cobra.Command, args []string) { root := cmd.Root() if root == nil { return } if root.Annotations == nil { root.Annotations = map[string]string{} } // Don't print debug message root.Annotations[agent.AgentExecutedAnnotation] = "true" }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") return c } type ProviderUpdateInfo struct { Available bool `json:"available,omitempty"` NewVersion string `json:"newVersion,omitempty"` } func (cmd *CheckUpdateCmd) Run(ctx context.Context, devPodConfig *config.Config, provider *provider.ProviderConfig) error { remoteVersion, err := platform.GetDevPodVersion(fmt.Sprintf("https://%s", cmd.Host)) if err != nil { return err } providerUpdate := ProviderUpdateInfo{} if provider.Version == versionpkg.DevVersion { providerUpdate.Available = false } else if provider.Version != remoteVersion { providerUpdate.Available = true providerUpdate.NewVersion = remoteVersion } out, err := json.Marshal(providerUpdate) if err != nil { return err } fmt.Print(string(out)) return nil } ================================================ FILE: cmd/pro/completion/suggestions.go ================================================ package completion import ( "strings" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) func GetPlatformHostSuggestions(rootCmd *cobra.Command, context, provider string, args []string, toComplete string, owner platform.OwnerFilter, logger log.Logger) ([]string, cobra.ShellCompDirective) { devPodConfig, err := config.LoadConfig(context, provider) if err != nil { return nil, cobra.ShellCompDirectiveError } proInstances, err := workspace.ListProInstances(devPodConfig, logger) if err != nil { return nil, cobra.ShellCompDirectiveError } var suggestions []string for _, instance := range proInstances { if strings.HasPrefix(instance.Host, toComplete) { suggestions = append(suggestions, instance.Host) } } return suggestions, cobra.ShellCompDirectiveNoFileComp } ================================================ FILE: cmd/pro/create_workspace.go ================================================ package pro import ( "bytes" "context" "fmt" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // CreateWorkspaceCmd holds the cmd flags type CreateWorkspaceCmd struct { *flags.GlobalFlags Log log.Logger Host string Instance string } // NewCreateWorkspaceCmd creates a new command func NewCreateWorkspaceCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &CreateWorkspaceCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "create-workspace", Short: "Create workspace instance", Hidden: true, RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") c.Flags().StringVar(&cmd.Instance, "instance", "", "The workspace instance to create") _ = c.MarkFlagRequired("instance") return c } func (cmd *CreateWorkspaceCmd) Run(ctx context.Context, devPodConfig *config.Config, provider *provider.ProviderConfig) error { opts := devPodConfig.ProviderOptions(provider.Name) opts[platform.WorkspaceInstanceEnv] = config.OptionValue{Value: cmd.Instance} var buf bytes.Buffer // ignore --debug because we tunnel json through stdio cmd.Log.SetLevel(logrus.InfoLevel) err := clientimplementation.RunCommandWithBinaries( ctx, "createWorkspace", provider.Exec.Proxy.Create.Workspace, devPodConfig.DefaultContext, nil, nil, opts, provider, nil, nil, &buf, cmd.Log.ErrorStreamOnly().Writer(logrus.ErrorLevel, true), cmd.Log) if err != nil { return fmt.Errorf("create workspace: %w", err) } fmt.Println(buf.String()) return nil } ================================================ FILE: cmd/pro/daemon/daemon.go ================================================ package daemon import ( "context" "fmt" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/config" providerpkg "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // NewCmd creates a new cobra command func NewCmd(globalFlags *flags.GlobalFlags) *cobra.Command { c := &cobra.Command{ Use: "daemon", Short: "DevPod Pro Provider daemon commands", Args: cobra.NoArgs, Hidden: true, } c.AddCommand(NewStartCmd(globalFlags)) c.AddCommand(NewStatusCmd(globalFlags)) c.AddCommand(NewNetcheckCmd(globalFlags)) return c } func findProProvider(ctx context.Context, context, provider, host string, log log.Logger) (*config.Config, *providerpkg.ProviderConfig, error) { devPodConfig, err := config.LoadConfig(context, provider) if err != nil { return nil, nil, err } pCfg, err := workspace.ProviderFromHost(ctx, devPodConfig, host, log) if err != nil { return devPodConfig, nil, fmt.Errorf("load provider: %w", err) } return devPodConfig, pCfg, nil } ================================================ FILE: cmd/pro/daemon/netcheck.go ================================================ package daemon import ( "context" "fmt" "strconv" "github.com/loft-sh/devpod/cmd/agent" "github.com/loft-sh/devpod/cmd/pro/completion" proflags "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/config" daemon "github.com/loft-sh/devpod/pkg/daemon/platform" providerpkg "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/spf13/cobra" "tailscale.com/client/tailscale" ) // NetcheckCmd holds the DevPod daemon flags type NetcheckCmd struct { *proflags.GlobalFlags Host string Log log.Logger } // NewNetcheckCmd creates a new command func NewNetcheckCmd(flags *proflags.GlobalFlags) *cobra.Command { cmd := &NetcheckCmd{ GlobalFlags: flags, Log: log.Default, } c := &cobra.Command{ Use: "netcheck", Short: "Get the status of the current network", RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, PersistentPreRun: func(cmd *cobra.Command, args []string) { root := cmd.Root() if root == nil { return } if root.Annotations == nil { root.Annotations = map[string]string{} } // Don't print debug message root.Annotations[agent.AgentExecutedAnnotation] = "true" }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") _ = c.RegisterFlagCompletionFunc("host", func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetPlatformHostSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, cmd.Log) }) return c } func (cmd *NetcheckCmd) Run(ctx context.Context, devPodConfig *config.Config, provider *providerpkg.ProviderConfig) error { tsClient := &tailscale.LocalClient{ Socket: daemon.GetSocketAddr(provider.Name), UseSocketOnly: true, } dm, err := tsClient.CurrentDERPMap(ctx) if err != nil { return err } for _, region := range dm.Regions { report, err := tsClient.DebugDERPRegion(ctx, strconv.Itoa(region.RegionID)) if err != nil { return err } msg := fmt.Sprintf("DERP %d (%s)\n", region.RegionID, region.RegionCode) if len(report.Errors) > 0 { for _, error := range report.Errors { msg += fmt.Sprintf(" Error: %s\n", error) } } if len(report.Warnings) > 0 { for _, warning := range report.Warnings { msg += fmt.Sprintf(" Warning: %s\n", warning) } } if len(report.Info) > 0 { for _, info := range report.Info { msg += fmt.Sprintf(" Info: %s\n", info) } } fmt.Println(msg) } return nil } ================================================ FILE: cmd/pro/daemon/start.go ================================================ package daemon import ( "context" "encoding/json" "fmt" "os" "os/signal" "path/filepath" "syscall" managementv1 "github.com/loft-sh/api/v4/pkg/apis/management/v1" daemon "github.com/loft-sh/devpod/pkg/daemon/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/cmd/pro/completion" proflags "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/config" providerpkg "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // StartCmd holds the devpod daemon flags type StartCmd struct { *proflags.GlobalFlags Host string Log log.Logger } // NewStartCmd creates a new command func NewStartCmd(flags *proflags.GlobalFlags) *cobra.Command { cmd := &StartCmd{ GlobalFlags: flags, Log: log.Default, } c := &cobra.Command{ Use: "start", Short: "Start the client daemon", RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") _ = c.RegisterFlagCompletionFunc("host", func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetPlatformHostSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, cmd.Log) }) return c } func (cmd *StartCmd) Run(ctx context.Context, devPodConfig *config.Config, provider *providerpkg.ProviderConfig) error { isDesktopControlled := os.Getenv("DEVPOD_UI") == "true" dir, err := ensureDaemonDir(devPodConfig.DefaultContext, provider.Name) if err != nil { return err } loftConfigPath := filepath.Join(dir, "..", "loft-config.json") baseClient, err := client.InitClientFromPath(ctx, loftConfigPath) if err != nil { if daemon.IsAccessKeyNotFound(err) && isDesktopControlled { printStatus(daemon.Status{State: daemon.DaemonStateStopped, LoginRequired: true}) return err } return err } userName := getUserName(baseClient.Self()) if userName == "" { return fmt.Errorf("user name not set") } // Create a context with signal handling ctx, cancel := withGracefulShutdown(ctx, cmd.Log) defer cancel() d, err := daemon.Init(ctx, daemon.InitConfig{ RootDir: dir, ProviderName: provider.Name, Context: devPodConfig.DefaultContext, UserName: userName, PlatformClient: baseClient, Debug: cmd.Debug, }) if err != nil { return fmt.Errorf("init daemon: %w", err) } if isDesktopControlled { printStatus(daemon.Status{State: daemon.DaemonStatePending}) } return d.Start(ctx) } // withGracefulShutdown returns a context that is canceled when termination signals are received. // It implements a two-phase shutdown where a second signal forces immediate termination. func withGracefulShutdown(ctx context.Context, log log.Logger) (context.Context, func()) { ctx, cancel := context.WithCancel(ctx) sigChan := make(chan os.Signal, 2) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT) go func() { for { select { case sig := <-sigChan: log.Infof("Received signal %s, starting graceful shutdown...", sig) cancel() case <-ctx.Done(): return } } }() go func() { <-ctx.Done() <-sigChan // force shutdown if context is done and we receive another signal os.Exit(1) }() return ctx, func() { cancel() signal.Stop(sigChan) } } func ensureDaemonDir(context, providerName string) (string, error) { tsDir, err := providerpkg.GetDaemonDir(context, providerName) if err != nil { return "", fmt.Errorf("get daemon dir: %w", err) } err = os.MkdirAll(tsDir, 0o700) if err != nil { return tsDir, fmt.Errorf("make daemon dir: %w", err) } return tsDir, nil } func printStatus(status daemon.Status) { out, err := json.Marshal(status) if err != nil { fmt.Printf("failed to marshal status: %v\n", err) os.Exit(1) } fmt.Println(string(out)) } func getUserName(self *managementv1.Self) string { if self.Status.User != nil { return self.Status.User.Name } if self.Status.Team != nil { return self.Status.Team.Name } return self.Status.Subject } ================================================ FILE: cmd/pro/daemon/status.go ================================================ package daemon import ( "context" "encoding/json" "fmt" platformdaemon "github.com/loft-sh/devpod/pkg/daemon/platform" "github.com/loft-sh/devpod/cmd/agent" "github.com/loft-sh/devpod/cmd/pro/completion" proflags "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/config" providerpkg "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // StatusCmd holds the DevPod daemon flags type StatusCmd struct { *proflags.GlobalFlags Host string Log log.Logger } // NewStatusCmd creates a new command func NewStatusCmd(flags *proflags.GlobalFlags) *cobra.Command { cmd := &StatusCmd{ GlobalFlags: flags, Log: log.Default, } c := &cobra.Command{ Use: "status", Short: "Get the status of the daemon", RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, PersistentPreRun: func(cmd *cobra.Command, args []string) { root := cmd.Root() if root == nil { return } if root.Annotations == nil { root.Annotations = map[string]string{} } // Don't print debug message root.Annotations[agent.AgentExecutedAnnotation] = "true" }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") _ = c.RegisterFlagCompletionFunc("host", func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetPlatformHostSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, cmd.Log) }) return c } func (cmd *StatusCmd) Run(ctx context.Context, devPodConfig *config.Config, provider *providerpkg.ProviderConfig) error { status, err := platformdaemon.NewLocalClient(provider.Name).Status(ctx, cmd.Debug) if err != nil { return err } out, err := json.Marshal(status) if err != nil { return err } fmt.Print(string(out)) return nil } ================================================ FILE: cmd/pro/delete.go ================================================ package pro import ( "context" "fmt" "os" "sync" "time" proflags "github.com/loft-sh/devpod/cmd/pro/flags" providercmd "github.com/loft-sh/devpod/cmd/provider" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" daemon "github.com/loft-sh/devpod/pkg/daemon/platform" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/util/wait" ) // DeleteCmd holds the delete cmd flags type DeleteCmd struct { *proflags.GlobalFlags IgnoreNotFound bool } // NewDeleteCmd creates a new command func NewDeleteCmd(flags *proflags.GlobalFlags) *cobra.Command { cmd := &DeleteCmd{ GlobalFlags: flags, } deleteCmd := &cobra.Command{ Use: "delete", Short: "Delete or logout from a DevPod Pro Instance", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, } deleteCmd.Flags().BoolVar(&cmd.IgnoreNotFound, "ignore-not-found", false, "Treat \"pro instance not found\" as a successful delete") return deleteCmd } func (cmd *DeleteCmd) Run(ctx context.Context, args []string) error { if len(args) != 1 { return fmt.Errorf("please specify an pro instance to delete") } devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } // load pro instance config proInstanceName := args[0] proInstanceConfig, err := provider.LoadProInstanceConfig(devPodConfig.DefaultContext, proInstanceName) if err != nil { if os.IsNotExist(err) && cmd.IgnoreNotFound { return nil } return fmt.Errorf("load pro instance %s: %w", proInstanceName, err) } providerConfig, err := provider.LoadProviderConfig(devPodConfig.DefaultContext, proInstanceConfig.Provider) if err != nil { return fmt.Errorf("load provider: %w", err) } // stop daemon and clean up local workspaces if providerConfig.IsDaemonProvider() { // clean up local workspaces workspaces, err := workspace.ListLocalWorkspaces(devPodConfig.DefaultContext, false, log.Default) if err != nil { log.Default.Warnf("Failed to list workspaces: %v", err) } else { cleanupLocalWorkspaces(ctx, devPodConfig, workspaces, providerConfig.Name, cmd.Owner, log.Default) } daemonClient := daemon.NewLocalClient(proInstanceConfig.Provider) err = daemonClient.Shutdown(ctx) if err != nil { log.Default.Warnf("Failed to shut down daemon: %v", err) } log.Default.Debug("Waiting for daemon to shut down") err = waitDaemonStopped(ctx, providerConfig.Name) if err != nil { log.Default.Warnf("Failed to wait for daemon to be stopped: %v", err) } } // delete the provider config err = providercmd.DeleteProviderConfig(devPodConfig, proInstanceConfig.Provider, true) if err != nil { return err } // delete the pro instance dir itself proInstanceDir, err := provider.GetProInstanceDir(devPodConfig.DefaultContext, proInstanceConfig.Host) if err != nil { return err } err = os.RemoveAll(proInstanceDir) if err != nil { return errors.Wrap(err, "delete pro instance dir") } log.Default.Donef("Successfully deleted pro instance '%s'", proInstanceName) return nil } func cleanupLocalWorkspaces(ctx context.Context, devPodConfig *config.Config, workspaces []*provider.Workspace, providerName string, owner platform.OwnerFilter, log log.Logger) { usedWorkspaces := []*provider.Workspace{} for _, workspace := range workspaces { if workspace.Provider.Name == providerName { usedWorkspaces = append(usedWorkspaces, workspace) } } if len(usedWorkspaces) > 0 { wg := sync.WaitGroup{} // try to force delete all workspaces in the background for _, w := range usedWorkspaces { wg.Add(1) go func(w provider.Workspace) { defer wg.Done() client, err := workspace.Get(ctx, devPodConfig, []string{w.ID}, true, owner, true, log) if err != nil { log.Errorf("Failed to get workspace %s: %v", w.ID, err) return } // delete workspace folder err = clientimplementation.DeleteWorkspaceFolder(devPodConfig.DefaultContext, client.Workspace(), client.WorkspaceConfig().SSHConfigPath, log) if err != nil { log.Errorf("Failed to remove workspace %s: %v", w.ID, err) return } log.Donef("Successfully removed workspace %s", w.ID) }(*w) } log.Infof("Waiting for %d workspace(s) to be removed locally", len(usedWorkspaces)) wg.Wait() } } func waitDaemonStopped(ctx context.Context, providerName string) error { return wait.PollUntilContextTimeout(ctx, 250*time.Millisecond, 5*time.Second, true, func(ctx context.Context) (done bool, err error) { _, err = daemon.Dial(daemon.GetSocketAddr(providerName)) if err != nil { return true, nil } return false, nil }) } ================================================ FILE: cmd/pro/flags/flags.go ================================================ package flags import ( "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/platform/client" flag "github.com/spf13/pflag" ) // GlobalFlags is the flags that contains the global flags type GlobalFlags struct { *flags.GlobalFlags Config string } // SetGlobalFlags applies the global flags func SetGlobalFlags(flags *flag.FlagSet) *GlobalFlags { globalFlags := &GlobalFlags{} flags.StringVar(&globalFlags.Config, "config", client.DefaultCacheConfig, "The config to use (will be created if it does not exist)") return globalFlags } ================================================ FILE: cmd/pro/import_workspace.go ================================================ package pro import ( "context" "fmt" "strconv" proflags "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/cmd/pro/provider/list" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/options" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/platform/parameters" "github.com/loft-sh/devpod/pkg/platform/project" provider2 "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/random" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" "gopkg.in/yaml.v2" managementv1 "github.com/loft-sh/api/v4/pkg/apis/management/v1" storagev1 "github.com/loft-sh/api/v4/pkg/apis/storage/v1" ) type ImportCmd struct { *proflags.GlobalFlags WorkspaceId string WorkspaceUid string WorkspaceProject string Own bool log log.Logger } // NewImportCmd creates a new command func NewImportCmd(globalFlags *proflags.GlobalFlags) *cobra.Command { logger := log.GetInstance() cmd := &ImportCmd{ GlobalFlags: globalFlags, log: logger, } importCmd := &cobra.Command{ Use: "import-workspace", Short: "Imports a workspace", RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), args) }, } importCmd.Flags().StringVar(&cmd.WorkspaceId, "workspace-id", "", "ID of a workspace to import") importCmd.Flags().StringVar(&cmd.WorkspaceUid, "workspace-uid", "", "UID of a workspace to import") importCmd.Flags().StringVar(&cmd.WorkspaceProject, "workspace-project", "", "Project of the workspace to import") importCmd.Flags().BoolVar(&cmd.Own, "own", false, "If true, will behave as if workspace was not imported") _ = importCmd.MarkFlagRequired("workspace-uid") return importCmd } func (cmd *ImportCmd) Run(ctx context.Context, args []string) error { if len(args) != 1 { return fmt.Errorf("usage: devpod pro import-workspace ") } devPodProHost := args[0] devPodConfig, err := config.LoadConfig(cmd.Context, "") if err != nil { return err } // set uid as id if cmd.WorkspaceId == "" { cmd.WorkspaceId = cmd.WorkspaceUid } // check if workspace already exists if provider2.WorkspaceExists(devPodConfig.DefaultContext, cmd.WorkspaceId) { workspaceConfig, err := provider2.LoadWorkspaceConfig(devPodConfig.DefaultContext, cmd.WorkspaceId) if err != nil { return fmt.Errorf("load workspace: %w", err) } else if workspaceConfig.UID == cmd.WorkspaceUid { cmd.log.Infof("Workspace %s already imported", cmd.WorkspaceId) return nil } newWorkspaceId := cmd.WorkspaceId + "-" + random.String(5) if provider2.WorkspaceExists(devPodConfig.DefaultContext, newWorkspaceId) { return fmt.Errorf("workspace %s already exists", cmd.WorkspaceId) } cmd.log.Infof("Workspace %s already exists, will use name %s instead", cmd.WorkspaceId, newWorkspaceId) cmd.WorkspaceId = newWorkspaceId } provider, err := workspace.ProviderFromHost(ctx, devPodConfig, devPodProHost, cmd.log) if err != nil { return fmt.Errorf("resolve provider: %w", err) } baseClient, err := platform.InitClientFromProvider(ctx, devPodConfig, provider.Name, cmd.log) if err != nil { return fmt.Errorf("base client: %w", err) } instance, err := platform.FindInstanceInProject(ctx, baseClient, cmd.WorkspaceUid, cmd.WorkspaceProject) if err != nil { return fmt.Errorf("find workspace instance: %w", err) } if instance == nil { return fmt.Errorf("workspace instance with UID %s not found", cmd.WorkspaceUid) } // old pro provider if !provider.HasHealthCheck() { instanceOpts, err := resolveInstanceOptions(ctx, instance, baseClient) if err != nil { return fmt.Errorf("resolve instance options: %w", err) } err = cmd.writeWorkspaceDefinition(devPodConfig, provider, instanceOpts, instance) if err != nil { return errors.Wrap(err, "prepare workspace to import definition") } cmd.log.Infof("Successfully imported workspace %s", cmd.WorkspaceId) return nil } // new pro provider err = cmd.writeNewWorkspaceDefinition(devPodConfig, instance, provider.Name) if err != nil { return errors.Wrap(err, "prepare workspace to import definition") } cmd.log.Infof("Successfully imported workspace %s", cmd.WorkspaceId) return nil } func (cmd *ImportCmd) writeNewWorkspaceDefinition(devPodConfig *config.Config, instance *managementv1.DevPodWorkspaceInstance, providerName string) error { workspaceObj := &provider2.Workspace{ ID: cmd.WorkspaceId, UID: cmd.WorkspaceUid, Provider: provider2.WorkspaceProviderConfig{Name: providerName}, Context: devPodConfig.DefaultContext, Imported: !cmd.Own, Pro: &provider2.ProMetadata{ InstanceName: instance.GetName(), Project: project.ProjectFromNamespace(instance.Namespace), DisplayName: instance.Spec.DisplayName, }, } return provider2.SaveWorkspaceConfig(workspaceObj) } func (cmd *ImportCmd) writeWorkspaceDefinition(devPodConfig *config.Config, provider *provider2.ProviderConfig, instanceOpts map[string]string, instance *managementv1.DevPodWorkspaceInstance) error { workspaceObj := &provider2.Workspace{ ID: cmd.WorkspaceId, UID: cmd.WorkspaceUid, Provider: provider2.WorkspaceProviderConfig{ Name: provider.Name, Options: map[string]config.OptionValue{}, }, Context: devPodConfig.DefaultContext, Imported: !cmd.Own, Pro: &provider2.ProMetadata{ InstanceName: instance.GetName(), Project: instanceOpts[platform.ProjectEnv], DisplayName: instance.Spec.DisplayName, }, } devPodConfig, err := options.ResolveOptions(context.Background(), devPodConfig, provider, instanceOpts, false, false, nil, cmd.log) if err != nil { return fmt.Errorf("resolve options: %w", err) } if devPodConfig.Current() == nil || devPodConfig.Current().Providers[provider.Name] == nil { return fmt.Errorf("unable to resolve provider config for provider %s", provider.Name) } workspaceObj.Provider.Options = devPodConfig.Current().Providers[provider.Name].Options err = provider2.SaveWorkspaceConfig(workspaceObj) if err != nil { return err } return nil } func resolveInstanceOptions(ctx context.Context, instance *managementv1.DevPodWorkspaceInstance, baseClient client.Client) (map[string]string, error) { opts := map[string]string{} projectName := project.ProjectFromNamespace(instance.Namespace) opts[platform.ProjectEnv] = projectName if instance.Spec.TemplateRef == nil { return opts, nil } //nolint:all if instance.Spec.RunnerRef.Runner != "" { opts[platform.RunnerEnv] = instance.Spec.RunnerRef.Runner //nolint:all } opts[platform.TemplateOptionEnv] = instance.Spec.TemplateRef.Name if instance.Spec.TemplateRef.Version != "" { opts[platform.TemplateVersionOptionEnv] = instance.Spec.TemplateRef.Version } if instance.Spec.Parameters == "" { return opts, nil } managementClient, err := baseClient.Management() if err != nil { return nil, fmt.Errorf("get management client: %w", err) } template, err := list.FindTemplate(ctx, managementClient, projectName, instance.Spec.TemplateRef.Name) if err != nil { return nil, fmt.Errorf("find template: %w", err) } templateParameters := template.Spec.Parameters if len(template.Spec.Versions) > 0 { templateParameters, err = list.GetTemplateParameters(template, instance.Spec.TemplateRef.Version) if err != nil { return nil, fmt.Errorf("get template parameters: %w", err) } } err = fillParameterOptions(opts, templateParameters, instance.Spec.Parameters) if err != nil { return nil, fmt.Errorf("fill parameter options: %w", err) } return opts, nil } func fillParameterOptions(opts map[string]string, parameterDefinitions []storagev1.AppParameter, instanceParameters string) error { parametersMap := map[string]interface{}{} err := yaml.Unmarshal([]byte(instanceParameters), ¶metersMap) if err != nil { return fmt.Errorf("unmarshal parameters: %w", err) } for _, parameter := range parameterDefinitions { val := parameters.GetDeepValue(parametersMap, parameter.Variable) var strVal string if val != nil { switch t := val.(type) { case string: strVal = t case int: strVal = strconv.Itoa(t) case bool: strVal = strconv.FormatBool(t) default: return fmt.Errorf("unrecognized type for parameter %s (%s) in file: %v", parameter.Label, parameter.Variable, t) } } _, err := parameters.VerifyValue(strVal, parameter) if err != nil { return err } optionName := list.VariableToEnvironmentVariable(parameter.Variable) opts[optionName] = strVal } return nil } ================================================ FILE: cmd/pro/list.go ================================================ package pro import ( "context" "encoding/json" "fmt" "sort" "time" proflags "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/loft-sh/log/table" "github.com/spf13/cobra" ) // ListCmd holds the list cmd flags type ListCmd struct { proflags.GlobalFlags Output string Login bool } // NewListCmd creates a new command func NewListCmd(flags *proflags.GlobalFlags) *cobra.Command { cmd := &ListCmd{ GlobalFlags: *flags, } listCmd := &cobra.Command{ Use: "list", Aliases: []string{"ls"}, Short: "List available DevPod Pro instances", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background()) }, } listCmd.Flags().StringVar(&cmd.Output, "output", "plain", "The output format to use. Can be json or plain") listCmd.Flags().BoolVar(&cmd.Login, "login", false, "Check if the user is logged into the pro instance") return listCmd } // Run runs the command logic func (cmd *ListCmd) Run(ctx context.Context) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } proInstances, err := workspace.ListProInstances(devPodConfig, log.Default) if err != nil { return err } if cmd.Output == "plain" { tableEntries := [][]string{} for _, proInstance := range proInstances { entry := []string{ proInstance.Host, proInstance.Provider, time.Since(proInstance.CreationTimestamp.Time).Round(1 * time.Second).String(), } if cmd.Login { err = checkLogin(ctx, devPodConfig, proInstance) entry = append(entry, fmt.Sprintf("%t", err == nil)) } tableEntries = append(tableEntries, entry) } sort.SliceStable(tableEntries, func(i, j int) bool { return tableEntries[i][0] < tableEntries[j][0] }) tableHeaders := []string{ "Host", "Provider", "Age", } if cmd.Login { tableHeaders = append(tableHeaders, "Authenticated") } table.PrintTable(log.Default, tableHeaders, tableEntries) } else if cmd.Output == "json" { tableEntries := []*proTableEntry{} for _, proInstance := range proInstances { entry := &proTableEntry{ ProInstance: proInstance, Context: devPodConfig.DefaultContext, Capabilities: getCapabilities(ctx, devPodConfig, proInstance, log.Discard), } if cmd.Login { err = checkLogin(ctx, devPodConfig, proInstance) isAuthenticated := err == nil entry.Authenticated = &isAuthenticated } tableEntries = append(tableEntries, entry) } sort.SliceStable(tableEntries, func(i, j int) bool { return tableEntries[i].Host < tableEntries[j].Host }) out, err := json.Marshal(tableEntries) if err != nil { return err } fmt.Print(string(out)) } else { return fmt.Errorf("unexpected output format, choose either json or plain. Got %s", cmd.Output) } return nil } type proTableEntry struct { *provider.ProInstance Authenticated *bool `json:"authenticated,omitempty"` Context string `json:"context,omitempty"` Capabilities []Capability `json:"capabilities,omitempty"` } type Capability string var ( capabilityDaemon Capability = "daemon" capabilityHealthCheck Capability = "health-check" capabilityUpdateProvider Capability = "update-provider" ) func checkLogin(ctx context.Context, devPodConfig *config.Config, proInstance *provider.ProInstance) error { // for every pro instance, check auth status by calling login if err := login(ctx, devPodConfig, proInstance.Host, proInstance.Provider, "", true, false, log.Default); err != nil { return fmt.Errorf("not logged into %s", proInstance.Host) } return nil } func getCapabilities(ctx context.Context, devPodConfig *config.Config, proInstance *provider.ProInstance, log log.Logger) []Capability { capabilities := []Capability{} provider, err := workspace.FindProvider(devPodConfig, proInstance.Provider, log) if err != nil { return capabilities } if provider.Config.HasHealthCheck() { capabilities = append(capabilities, capabilityHealthCheck) capabilities = append(capabilities, capabilityUpdateProvider) } if provider.Config.IsDaemonProvider() { capabilities = append(capabilities, capabilityDaemon) } return capabilities } ================================================ FILE: cmd/pro/list_clusters.go ================================================ package pro import ( "bytes" "context" "fmt" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // ListClustersCmd holds the cmd flags type ListClustersCmd struct { *flags.GlobalFlags Log log.Logger Host string Project string } // NewListClustersCmd creates a new command func NewListClustersCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &ListClustersCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "list-clusters", Short: "List clusters", Hidden: true, RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") c.Flags().StringVar(&cmd.Project, "project", "", "The project to use") _ = c.MarkFlagRequired("project") return c } func (cmd *ListClustersCmd) Run(ctx context.Context, devPodConfig *config.Config, provider *provider.ProviderConfig) error { opts := devPodConfig.ProviderOptions(provider.Name) opts[platform.ProjectEnv] = config.OptionValue{Value: cmd.Project} // ignore --debug because we tunnel json through stdio cmd.Log.SetLevel(logrus.InfoLevel) var buf bytes.Buffer err := clientimplementation.RunCommandWithBinaries( ctx, "listClusters", provider.Exec.Proxy.List.Clusters, devPodConfig.DefaultContext, nil, nil, opts, provider, nil, nil, &buf, nil, cmd.Log) if err != nil { return fmt.Errorf("list clusters with provider \"%s\": %w", provider.Name, err) } fmt.Println(buf.String()) return nil } ================================================ FILE: cmd/pro/list_projects.go ================================================ package pro import ( "bytes" "context" "fmt" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // ListProjectsCmd holds the cmd flags type ListProjectsCmd struct { *flags.GlobalFlags Log log.Logger Host string } // NewListProjectsCmd creates a new command func NewListProjectsCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &ListProjectsCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "list-projects", Short: "List projects", Hidden: true, RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") return c } func (cmd *ListProjectsCmd) Run(ctx context.Context, devPodConfig *config.Config, provider *provider.ProviderConfig) error { var buf bytes.Buffer // ignore --debug because we tunnel json through stdio cmd.Log.SetLevel(logrus.InfoLevel) err := clientimplementation.RunCommandWithBinaries( ctx, "listProjects", provider.Exec.Proxy.List.Projects, devPodConfig.DefaultContext, nil, nil, devPodConfig.ProviderOptions(provider.Name), provider, nil, nil, &buf, nil, cmd.Log) if err != nil { return fmt.Errorf("watch workspaces with provider \"%s\": %w", provider.Name, err) } fmt.Println(buf.String()) return nil } ================================================ FILE: cmd/pro/list_templates.go ================================================ package pro import ( "bytes" "context" "fmt" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // ListTemplatesCmd holds the cmd flags type ListTemplatesCmd struct { *flags.GlobalFlags Log log.Logger Host string Project string } // NewListTemplatesCmd creates a new command func NewListTemplatesCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &ListTemplatesCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "list-templates", Short: "List templates", Hidden: true, RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") c.Flags().StringVar(&cmd.Project, "project", "", "The project to use") _ = c.MarkFlagRequired("project") return c } func (cmd *ListTemplatesCmd) Run(ctx context.Context, devPodConfig *config.Config, provider *provider.ProviderConfig) error { opts := devPodConfig.ProviderOptions(provider.Name) opts[platform.ProjectEnv] = config.OptionValue{Value: cmd.Project} // ignore --debug because we tunnel json through stdio cmd.Log.SetLevel(logrus.InfoLevel) var buf bytes.Buffer err := clientimplementation.RunCommandWithBinaries( ctx, "listTemplates", provider.Exec.Proxy.List.Templates, devPodConfig.DefaultContext, nil, nil, opts, provider, nil, nil, &buf, nil, cmd.Log) if err != nil { return fmt.Errorf("list templates with provider \"%s\": %w", provider.Name, err) } fmt.Println(buf.String()) return nil } ================================================ FILE: cmd/pro/list_workspaces.go ================================================ package pro import ( "bytes" "context" "fmt" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // ListWorkspacesCmd holds the cmd flags type ListWorkspacesCmd struct { *flags.GlobalFlags Log log.Logger Host string } // NewListWorkspacesCmd creates a new command func NewListWorkspacesCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &ListWorkspacesCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "list-workspaces", Short: "List Workspaces", Hidden: true, RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") return c } func (cmd *ListWorkspacesCmd) Run(ctx context.Context, devPodConfig *config.Config, provider *provider.ProviderConfig) error { var buf bytes.Buffer // ignore --debug because we tunnel json through stdio cmd.Log.SetLevel(logrus.InfoLevel) err := clientimplementation.RunCommandWithBinaries( ctx, "listWorkspaces", provider.Exec.Proxy.List.Workspaces, devPodConfig.DefaultContext, nil, nil, devPodConfig.ProviderOptions(provider.Name), provider, nil, nil, &buf, nil, cmd.Log) if err != nil { return fmt.Errorf("list workspaces: %w", err) } fmt.Println(buf.String()) return nil } ================================================ FILE: cmd/pro/login.go ================================================ package pro import ( "context" "fmt" "net/url" "strings" "github.com/blang/semver" proflags "github.com/loft-sh/devpod/cmd/pro/flags" providercmd "github.com/loft-sh/devpod/cmd/provider" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/types" versionpkg "github.com/loft-sh/devpod/pkg/version" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/mgutz/ansi" "github.com/pkg/errors" "github.com/spf13/cobra" ) const PROVIDER_BINARY = "PRO_PROVIDER" const providerRepo = "loft-sh/devpod" // LoginCmd holds the login cmd flags type LoginCmd struct { proflags.GlobalFlags AccessKey string Provider string Version string ProviderSource string Options []string Login bool Use bool ForceBrowser bool } // NewLoginCmd creates a new command func NewLoginCmd(flags *proflags.GlobalFlags) *cobra.Command { cmd := &LoginCmd{ GlobalFlags: *flags, } loginCmd := &cobra.Command{ Use: "login", Short: "Log into a DevPod Pro instance", RunE: func(_ *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("please specify the DevPod Pro host, e.g. devpod pro login my-pro.my-domain.com") } return cmd.Run(context.Background(), args[0], log.Default) }, } loginCmd.Flags().StringVar(&cmd.AccessKey, "access-key", "", "If defined will use the given access key to login") loginCmd.Flags().BoolVar(&cmd.Login, "login", true, "If enabled will automatically try to log into the Loft DevPod Pro") loginCmd.Flags().BoolVar(&cmd.Use, "use", true, "If enabled will automatically activate the provider") loginCmd.Flags().StringVar(&cmd.Provider, "provider", "", "Optional name how the DevPod Pro provider will be named") loginCmd.Flags().StringVar(&cmd.Version, "version", "", "The version to use for the DevPod provider") loginCmd.Flags().StringArrayVarP(&cmd.Options, "option", "o", []string{}, "Provider option in the form KEY=VALUE") loginCmd.Flags().BoolVar(&cmd.ForceBrowser, "force-browser", false, "Force login through browser") loginCmd.Flags().StringVar(&cmd.ProviderSource, "provider-source", "", "The source of the provider") _ = loginCmd.Flags().MarkHidden("provider-source") return loginCmd } // Run runs the command logic func (cmd *LoginCmd) Run(ctx context.Context, fullURL string, log log.Logger) error { if strings.HasPrefix(fullURL, "http://") { return fmt.Errorf("http is not supported for DevPod Pro, please use https:// instead") } else if !strings.HasPrefix(fullURL, "https://") { fullURL = "https://" + fullURL } else if cmd.Provider != "" && len(cmd.Provider) > 32 { return fmt.Errorf("cannot use a provider name greater than 32 characters") } // get host from url parsedURL, err := url.Parse(fullURL) if err != nil { return fmt.Errorf("invalid url %s: %w", fullURL, err) } // extract host host := parsedURL.Host // load devpod config devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } // check if there is already a pro instance with that url proInstances, err := workspace.ListProInstances(devPodConfig, log) if err != nil { return err } // check if url is found somewhere var currentInstance *provider.ProInstance for _, proInstance := range proInstances { if proInstance.Host == host { currentInstance = proInstance break } } if currentInstance != nil { cmd.Provider = currentInstance.Provider } else { // find a provider name if cmd.Provider == "" { cmd.Provider = "devpod-pro" } cmd.Provider = provider.ToProInstanceID(cmd.Provider) // check if provider already exists providers, err := workspace.LoadAllProviders(devPodConfig, log) if err != nil { return fmt.Errorf("load providers: %w", err) } // provider already exists? if providers[cmd.Provider] != nil { // alternative name cmd.Provider = provider.ToProInstanceID("devpod-" + host) if providers[cmd.Provider] != nil { return fmt.Errorf("provider %s already exists, please choose a different name via --provider", cmd.Provider) } } } // 1. Add provider if currentInstance == nil { currentInstance = &provider.ProInstance{ Provider: cmd.Provider, Host: host, CreationTimestamp: types.Now(), } remoteVersion, err := platform.GetDevPodVersion(fullURL) if err != nil { return err } rv, err := semver.Parse(strings.TrimPrefix(remoteVersion, "v")) if err != nil { return fmt.Errorf("invalid version %s: %w", remoteVersion, err) } if rv.LT(semver.Version{Major: 0, Minor: 6, Patch: 999}) && remoteVersion != versionpkg.DevVersion { log.Debug("remote version < 0.7.0, installing proxy provider") // proxy providers are deprecated and shouldn't be used // unless explicitly the server version is below 0.7.0 err = cmd.addLoftProvider(devPodConfig, fullURL, log) if err != nil { return err } } else { // add built-in pro (daemon) provider _, err = workspace.AddProvider(devPodConfig, cmd.Provider, "pro", log) if err != nil { return err } } err = provider.SaveProInstanceConfig(devPodConfig.DefaultContext, currentInstance) if err != nil { return err } // reload devpod config devPodConfig, err = config.LoadConfig(devPodConfig.DefaultContext, cmd.Provider) if err != nil { return err } } // get provider config providerConfig, err := provider.LoadProviderConfig(devPodConfig.DefaultContext, cmd.Provider) if err != nil { return err } // 2. Login to Loft if cmd.Login { err = login(ctx, devPodConfig, fullURL, cmd.Provider, cmd.AccessKey, false, cmd.ForceBrowser, log) if err != nil { return err } log.Donef("Successfully logged into DevPod Pro instance %s", ansi.Color(fullURL, "white+b")) } // 3. Configure provider if cmd.Use { err := providercmd.ConfigureProvider(ctx, providerConfig, devPodConfig.DefaultContext, cmd.Options, false, false, false, nil, log) if err != nil { return errors.Wrap(err, "configure provider") } } log.Donef("Successfully configured DevPod Pro") return nil } func (cmd *LoginCmd) addLoftProvider(devPodConfig *config.Config, url string, log log.Logger) error { // find out loft version err := cmd.resolveProviderSource(url) if err != nil { return err } // add the provider log.Infof("Add DevPod Pro provider...") // is development? if cmd.ProviderSource == providerRepo+"@v0.0.0" { log.Debugf("Add development provider") _, err = workspace.AddProviderRaw(devPodConfig, cmd.Provider, &provider.ProviderSource{}, []byte(fallbackProvider), log) if err != nil { return err } } else { _, err = workspace.AddProvider(devPodConfig, cmd.Provider, cmd.ProviderSource, log) if err != nil { return err } } return nil } func (cmd *LoginCmd) resolveProviderSource(url string) error { if cmd.ProviderSource != "" { return nil } if cmd.Version != "" { cmd.ProviderSource = providerRepo + "@" + cmd.Version return nil } version, err := platform.GetDevPodVersion(url) if err != nil { return fmt.Errorf("get version: %w", err) } cmd.ProviderSource = providerRepo + "@" + version return nil } func login(ctx context.Context, devPodConfig *config.Config, url string, providerName string, accessKey string, skipBrowserLogin, forceBrowser bool, log log.Logger) error { configPath, err := platform.LoftConfigPath(devPodConfig.DefaultContext, providerName) if err != nil { return err } loader, err := client.NewClientFromPath(configPath) if err != nil { return err } if !strings.HasPrefix(url, "http") { url = "https://" + url } if accessKey == "" { accessKey = loader.Config().AccessKey } // log in url = strings.TrimSuffix(url, "/") if accessKey != "" && !forceBrowser { err = loader.LoginWithAccessKey(url, accessKey, true, true) } else { if skipBrowserLogin { return fmt.Errorf("unable to login to loft host") } err = loader.Login(url, true, log) } if err != nil { return err } return nil } var fallbackProvider = `name: devpod-pro version: v0.0.0 icon: https://devpod.sh/assets/devpod.svg description: DevPod Pro options: LOFT_CONFIG: global: true hidden: true required: true default: "${PROVIDER_FOLDER}/loft-config.json" binaries: PRO_PROVIDER: - os: linux arch: amd64 path: /usr/local/bin/devpod - os: linux arch: arm64 path: /usr/local/bin/devpod - os: darwin arch: amd64 path: /usr/local/bin/devpod - os: darwin arch: arm64 path: /usr/local/bin/devpod - os: windows arch: amd64 path: "C:\\Users\\pasca\\workspace\\devpod\\desktop\\src-tauri\\bin\\devpod-cli-x86_64-pc-windows-msvc.exe" exec: proxy: up: |- ${PRO_PROVIDER} pro provider up ssh: |- ${PRO_PROVIDER} pro provider ssh stop: |- ${PRO_PROVIDER} pro provider stop status: |- ${PRO_PROVIDER} pro provider status delete: |- ${PRO_PROVIDER} pro provider delete health: |- ${PRO_PROVIDER} pro provider health daemon: start: |- ${PRO_PROVIDER} pro provider daemon start status: |- ${PRO_PROVIDER} pro provider daemon status create: workspace: |- ${PRO_PROVIDER} pro provider create workspace get: workspace: |- ${PRO_PROVIDER} pro provider get workspace self: |- ${PRO_PROVIDER} pro provider get self version: |- ${PRO_PROVIDER} pro provider get version update: workspace: |- ${PRO_PROVIDER} pro provider update workspace watch: workspaces: |- ${PRO_PROVIDER} pro provider watch workspaces list: workspaces: |- ${PRO_PROVIDER} pro provider list workspaces projects: |- ${PRO_PROVIDER} pro provider list projects templates: |- ${PRO_PROVIDER} pro provider list templates clusters: |- ${PRO_PROVIDER} pro provider list clusters ` ================================================ FILE: cmd/pro/pro.go ================================================ package pro import ( "context" "fmt" "os" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/cmd/pro/add" "github.com/loft-sh/devpod/cmd/pro/daemon" proflags "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/cmd/pro/provider" "github.com/loft-sh/devpod/cmd/pro/reset" "github.com/loft-sh/devpod/pkg/config" providerpkg "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // NewProCmd returns a new command func NewProCmd(flags *flags.GlobalFlags, streamLogger *log.StreamLogger) *cobra.Command { globalFlags := &proflags.GlobalFlags{GlobalFlags: flags} proCmd := &cobra.Command{ Use: "pro", Short: "DevPod Pro commands", SilenceUsage: true, SilenceErrors: true, Args: cobra.NoArgs, PersistentPreRunE: func(c *cobra.Command, _ []string) error { globalFlags = proflags.SetGlobalFlags(c.PersistentFlags()) if flags.Silent { streamLogger.SetLevel(logrus.FatalLevel) } if flags.Debug { streamLogger.SetLevel(logrus.DebugLevel) } if os.Getenv("DEVPOD_DEBUG") == "true" { log.Default.SetLevel(logrus.DebugLevel) } if flags.LogOutput == "json" { log.Default.SetFormat(log.JSONFormat) } return nil }, } proCmd.AddCommand(NewLoginCmd(globalFlags)) proCmd.AddCommand(NewListCmd(globalFlags)) proCmd.AddCommand(NewDeleteCmd(globalFlags)) proCmd.AddCommand(NewImportCmd(globalFlags)) proCmd.AddCommand(NewStartCmd(globalFlags)) proCmd.AddCommand(NewRebuildCmd(globalFlags)) proCmd.AddCommand(NewSleepCmd(globalFlags)) proCmd.AddCommand(NewWakeupCmd(globalFlags)) proCmd.AddCommand(reset.NewResetCmd(globalFlags)) proCmd.AddCommand(provider.NewProProviderCmd(globalFlags)) proCmd.AddCommand(daemon.NewCmd(globalFlags)) proCmd.AddCommand(add.NewAddCmd(globalFlags)) proCmd.AddCommand(NewWatchWorkspacesCmd(globalFlags)) proCmd.AddCommand(NewSelfCmd(globalFlags)) proCmd.AddCommand(NewVersionCmd(globalFlags)) proCmd.AddCommand(NewListProjectsCmd(globalFlags)) proCmd.AddCommand(NewListWorkspacesCmd(globalFlags)) proCmd.AddCommand(NewListTemplatesCmd(globalFlags)) proCmd.AddCommand(NewListClustersCmd(globalFlags)) proCmd.AddCommand(NewCreateWorkspaceCmd(globalFlags)) proCmd.AddCommand(NewUpdateWorkspaceCmd(globalFlags)) proCmd.AddCommand(NewCheckHealthCmd(globalFlags)) proCmd.AddCommand(NewCheckUpdateCmd(globalFlags)) proCmd.AddCommand(NewUpdateProviderCmd(globalFlags)) return proCmd } func findProProvider(ctx context.Context, context, provider, host string, log log.Logger) (*config.Config, *providerpkg.ProviderConfig, error) { devPodConfig, err := config.LoadConfig(context, provider) if err != nil { return nil, nil, err } pCfg, err := workspace.ProviderFromHost(ctx, devPodConfig, host, log) if err != nil { return devPodConfig, nil, fmt.Errorf("load provider: %w", err) } return devPodConfig, pCfg, nil } ================================================ FILE: cmd/pro/provider/create/create.go ================================================ package create import ( "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/spf13/cobra" ) // NewCmd creates a new cobra command func NewCmd(globalFlags *flags.GlobalFlags) *cobra.Command { c := &cobra.Command{ Use: "create", Short: "DevPod Pro Provider create commands", Args: cobra.NoArgs, Hidden: true, } c.AddCommand(NewWorkspaceCmd(globalFlags)) return c } ================================================ FILE: cmd/pro/provider/create/workspace.go ================================================ package create import ( "context" "encoding/json" "fmt" "io" "os" managementv1 "github.com/loft-sh/api/v4/pkg/apis/management/v1" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/platform/form" "github.com/loft-sh/devpod/pkg/platform/project" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/loft-sh/log/terminal" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // WorkspaceCmd holds the cmd flags type WorkspaceCmd struct { *flags.GlobalFlags Log log.Logger } // NewWorkspaceCmd creates a new command func NewWorkspaceCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &WorkspaceCmd{ GlobalFlags: globalFlags, Log: log.GetInstance().ErrorStreamOnly(), } c := &cobra.Command{ Use: "workspace", Short: "Create a workspace", Hidden: true, Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), os.Stdin, os.Stdout, os.Stderr) }, } return c } func (cmd *WorkspaceCmd) Run(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } // fully serialized intance, right now only used by GUI instanceEnv := os.Getenv(platform.WorkspaceInstanceEnv) if instanceEnv != "" { instance := &managementv1.DevPodWorkspaceInstance{} // init pointer err := json.Unmarshal([]byte(instanceEnv), instance) if err != nil { return fmt.Errorf("unmarshal workpace instance %s: %w", instanceEnv, err) } updatedInstance, err := createInstance(ctx, baseClient, instance, cmd.Log) if err != nil { return err } out, err := json.Marshal(updatedInstance) if err != nil { return err } fmt.Println(string(out)) return nil } // Info through env, right now only used by CLI workspaceID := os.Getenv(provider.WORKSPACE_ID) workspaceUID := os.Getenv(provider.WORKSPACE_UID) workspaceFolder := os.Getenv(provider.WORKSPACE_FOLDER) workspaceContext := os.Getenv(provider.WORKSPACE_CONTEXT) workspacePicture := os.Getenv(platform.WorkspacePictureEnv) workspaceSource := os.Getenv(platform.WorkspaceSourceEnv) if workspaceUID == "" || workspaceID == "" || workspaceFolder == "" { return fmt.Errorf("workspaceID, workspaceUID or workspace folder not found: %s, %s, %s", workspaceID, workspaceUID, workspaceFolder) } instance, err := platform.FindInstance(ctx, baseClient, workspaceUID) if err != nil { return err } // Nothing left to do if we already have an instance if instance != nil { return nil } if !terminal.IsTerminalIn { return fmt.Errorf("unable to create new instance through CLI if stdin is not a terminal") } instance, err = form.CreateInstance(ctx, baseClient, workspaceID, workspaceUID, workspaceSource, workspacePicture, cmd.Log) if err != nil { return err } _, err = createInstance(ctx, baseClient, instance, cmd.Log) if err != nil { return err } // once we have the instance, update workspace and save config // TODO: Do we need a file lock? workspaceConfig, err := provider.LoadWorkspaceConfig(workspaceContext, workspaceID) if err != nil { return fmt.Errorf("load workspace config: %w", err) } workspaceConfig.Pro = &provider.ProMetadata{ InstanceName: instance.GetName(), Project: project.ProjectFromNamespace(instance.GetNamespace()), DisplayName: instance.Spec.DisplayName, } err = provider.SaveWorkspaceConfig(workspaceConfig) if err != nil { return fmt.Errorf("save workspace config: %w", err) } return nil } func createInstance(ctx context.Context, client client.Client, instance *managementv1.DevPodWorkspaceInstance, log log.Logger) (*managementv1.DevPodWorkspaceInstance, error) { managementClient, err := client.Management() if err != nil { return nil, err } updatedInstance, err := managementClient.Loft().ManagementV1(). DevPodWorkspaceInstances(instance.GetNamespace()). Create(ctx, instance, metav1.CreateOptions{}) if err != nil { return nil, fmt.Errorf("create workspace instance: %w", err) } return platform.WaitForInstance(ctx, client, updatedInstance, log) } ================================================ FILE: cmd/pro/provider/delete.go ================================================ package provider import ( "context" "fmt" "io" "os" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/log" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // DeleteCmd holds the cmd flags type DeleteCmd struct { *flags.GlobalFlags Log log.Logger } // NewDeleteCmd creates a new command func NewDeleteCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &DeleteCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Hidden: true, Use: "delete", Short: "Runs delete on a workspace", Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), os.Stdin, os.Stdout, os.Stderr) }, } return c } func (cmd *DeleteCmd) Run(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } info, err := platform.GetWorkspaceInfoFromEnv() if err != nil { return err } workspace, err := platform.FindInstanceInProject(ctx, baseClient, info.UID, info.ProjectName) if err != nil { return err } else if workspace == nil { return fmt.Errorf("couldn't find workspace") } managementClient, err := baseClient.Management() if err != nil { return err } err = managementClient.Loft().ManagementV1().DevPodWorkspaceInstances(workspace.Namespace).Delete(ctx, workspace.Name, metav1.DeleteOptions{}) if err != nil { return fmt.Errorf("delete workspace: %w", err) } return nil } ================================================ FILE: cmd/pro/provider/get/get.go ================================================ package get import ( "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/spf13/cobra" ) // NewCmd creates a new cobra command func NewCmd(globalFlags *flags.GlobalFlags) *cobra.Command { c := &cobra.Command{ Use: "get", Short: "DevPod Pro Provider get commands", Args: cobra.NoArgs, Hidden: true, } c.AddCommand(NewWorkspaceCmd(globalFlags)) c.AddCommand(NewSelfCmd(globalFlags)) c.AddCommand(NewVersionCmd(globalFlags)) return c } ================================================ FILE: cmd/pro/provider/get/self.go ================================================ package get import ( "context" "encoding/json" "fmt" "io" "os" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // SelfCmd holds the cmd flags type SelfCmd struct { *flags.GlobalFlags Log log.Logger } // NewSelfCmd creates a new command func NewSelfCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &SelfCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "self", Short: "Get self", Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), os.Stdin, os.Stdout, os.Stderr) }, } return c } func (cmd *SelfCmd) Run(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } out, err := json.Marshal(baseClient.Self()) if err != nil { return err } fmt.Println(string(out)) return nil } ================================================ FILE: cmd/pro/provider/get/version.go ================================================ package get import ( "context" "encoding/json" "fmt" "io" "os" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // VersionCmd holds the cmd flags type VersionCmd struct { *flags.GlobalFlags Log log.Logger } type VersionInfo struct { // ServerVersion is the platform deployment version ServerVersion string `json:"serverVersion,omitempty"` // RemoteProviderVersion is the desired provider version of the current platform deployment RemoteProviderVersion string `json:"remoteProviderVersion,omitempty"` // CurrentProviderVersion is the currently installed provider version CurrentProviderVersion string `json:"currentProviderVersion,omitempty"` } // NewVersionCmd creates a new command func NewVersionCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &VersionCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "version", Short: "Get platform version", Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), os.Stdin, os.Stdout, os.Stderr) }, } return c } func (cmd *VersionCmd) Run(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } providerContext := os.Getenv(provider.PROVIDER_CONTEXT) if providerContext == "" { providerContext = config.DefaultContext } providerID := os.Getenv(provider.PROVIDER_ID) if providerID == "" { return fmt.Errorf("provider ID %s not defined", providerID) } // get our own version providerConfig, err := provider.LoadProviderConfig(providerContext, providerID) if err != nil { return err } providerVersion := providerConfig.Version // get platform version platformVersion, err := platform.GetPlatformVersion(baseClient.Config().Host) if err != nil { return err } v := VersionInfo{ ServerVersion: platformVersion.Version, RemoteProviderVersion: platformVersion.DevPodVersion, CurrentProviderVersion: providerVersion, } out, err := json.Marshal(v) if err != nil { return err } fmt.Println(string(out)) return nil } ================================================ FILE: cmd/pro/provider/get/workspace.go ================================================ package get import ( "context" "encoding/json" "fmt" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // WorkspaceCmd holds the cmd flags type WorkspaceCmd struct { *flags.GlobalFlags log log.Logger } // NewWorkspaceCmd creates a new command func NewWorkspaceCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &WorkspaceCmd{ GlobalFlags: globalFlags, log: log.GetInstance(), } c := &cobra.Command{ Use: "workspace", Short: "Get workspace for the provider", Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context()) }, } return c } func (cmd *WorkspaceCmd) Run(ctx context.Context) error { baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } workspaceInfo, err := platform.GetWorkspaceInfoFromEnv() if err != nil { return err } instance, err := platform.FindInstanceInProject(ctx, baseClient, workspaceInfo.UID, workspaceInfo.ProjectName) if err != nil { return err } instanceBytes, err := json.Marshal(instance) if err != nil { return nil } fmt.Println(string(instanceBytes)) return nil } ================================================ FILE: cmd/pro/provider/health.go ================================================ package provider import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url" "os" "github.com/loft-sh/devpod/cmd/pro/flags" devpodhttp "github.com/loft-sh/devpod/pkg/http" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // HealthCmd holds the cmd flags type HealthCmd struct { *flags.GlobalFlags Log log.Logger } // NewHealthCmd creates a new command func NewHealthCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &HealthCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "health", Short: "Check platform health", Hidden: true, Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), os.Stdin, os.Stdout, os.Stderr) }, } return c } func (cmd *HealthCmd) Run(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } config := baseClient.Config() u, err := url.Parse(fmt.Sprintf("%s/healthz", config.Host)) if err != nil { return err } res, err := devpodhttp.GetHTTPClient().Get(u.String()) if err != nil { return err } healthCheck := HealthCheck{ Healthy: res.StatusCode == http.StatusOK, } out, err := json.Marshal(healthCheck) if err != nil { return err } fmt.Println(string(out)) return nil } type HealthCheck struct { Healthy bool `json:"healthy,omitempty"` } ================================================ FILE: cmd/pro/provider/list/clusters.go ================================================ package list import ( "context" "encoding/json" "fmt" "os" managementv1 "github.com/loft-sh/api/v4/pkg/apis/management/v1" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/log" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ClustersCmd holds the cmd flags type ClustersCmd struct { *flags.GlobalFlags log log.Logger } // NewClustersCmd creates a new command func NewClustersCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &ClustersCmd{ GlobalFlags: globalFlags, log: log.GetInstance(), } c := &cobra.Command{ Use: "clusters", Short: "Lists clusters for the provider", Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context()) }, } return c } func (cmd *ClustersCmd) Run(ctx context.Context) error { projectName := os.Getenv(platform.ProjectEnv) if projectName == "" { return fmt.Errorf("%s environment variable is empty", platform.ProjectEnv) } baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } clustersList, err := Clusters(ctx, baseClient, projectName) if err != nil { return err } out, err := json.Marshal(clustersList) if err != nil { return err } fmt.Println(string(out)) return nil } func Clusters(ctx context.Context, client client.Client, projectName string) (*managementv1.ProjectClusters, error) { managementClient, err := client.Management() if err != nil { return nil, err } clustersList, err := managementClient.Loft().ManagementV1().Projects().ListClusters(ctx, projectName, metav1.GetOptions{}) if err != nil { return clustersList, fmt.Errorf("list clusters: %w", err) } return clustersList, nil } ================================================ FILE: cmd/pro/provider/list/list.go ================================================ package list import ( "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/spf13/cobra" ) // NewCmd creates a new cobra command func NewCmd(globalFlags *flags.GlobalFlags) *cobra.Command { c := &cobra.Command{ Use: "list", Short: "DevPod Pro Provider list commands", Args: cobra.NoArgs, Hidden: true, } c.AddCommand(NewWorkspacesCmd(globalFlags)) c.AddCommand(NewProjectsCmd(globalFlags)) c.AddCommand(NewTemplatesCmd(globalFlags)) c.AddCommand(NewClustersCmd(globalFlags)) return c } ================================================ FILE: cmd/pro/provider/list/projects.go ================================================ package list import ( "context" "encoding/json" "fmt" managementv1 "github.com/loft-sh/api/v4/pkg/apis/management/v1" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/log" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ProjectsCmd holds the cmd flags type ProjectsCmd struct { *flags.GlobalFlags log log.Logger } // NewProjectsCmd creates a new command func NewProjectsCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &ProjectsCmd{ GlobalFlags: globalFlags, log: log.GetInstance(), } c := &cobra.Command{ Use: "projects", Short: "Lists projects for the provider", Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context()) }, } return c } func (cmd *ProjectsCmd) Run(ctx context.Context) error { baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } projectList, err := Projects(ctx, baseClient) if err != nil { return err } out, err := json.Marshal(projectList.Items) if err != nil { return err } fmt.Println(string(out)) return nil } func Projects(ctx context.Context, client client.Client) (*managementv1.ProjectList, error) { managementClient, err := client.Management() if err != nil { return nil, err } projectList, err := managementClient.Loft().ManagementV1().Projects().List(ctx, metav1.ListOptions{}) if err != nil { return projectList, fmt.Errorf("list projects: %w", err) } else if len(projectList.Items) == 0 { return projectList, fmt.Errorf("you don't have access to any projects, please make sure you have at least access to 1 project") } return projectList, nil } ================================================ FILE: cmd/pro/provider/list/templates.go ================================================ package list import ( "context" "encoding/json" "fmt" "os" "regexp" "strconv" "strings" "github.com/blang/semver" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/platform/kube" "github.com/loft-sh/log" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" managementv1 "github.com/loft-sh/api/v4/pkg/apis/management/v1" storagev1 "github.com/loft-sh/api/v4/pkg/apis/storage/v1" ) // TemplatesCmd holds the cmd flags type TemplatesCmd struct { *flags.GlobalFlags log log.Logger } // NewTemplatesCmd creates a new command func NewTemplatesCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &TemplatesCmd{ GlobalFlags: globalFlags, log: log.GetInstance(), } c := &cobra.Command{ Use: "templates", Short: "Lists templates for the provider", Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context()) }, } return c } func (cmd *TemplatesCmd) Run(ctx context.Context) error { projectName := os.Getenv(platform.ProjectEnv) if projectName == "" { return fmt.Errorf("%s environment variable is empty", platform.ProjectEnv) } baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } templates, err := Templates(ctx, baseClient, projectName) if err != nil { return err } out, err := json.Marshal(templates) if err != nil { return err } fmt.Println(string(out)) return nil } func Templates(ctx context.Context, client client.Client, projectName string) (*managementv1.ProjectTemplates, error) { managementClient, err := client.Management() if err != nil { return nil, err } templateList, err := managementClient.Loft().ManagementV1().Projects().ListTemplates(ctx, projectName, metav1.GetOptions{}) if err != nil { return templateList, fmt.Errorf("list templates: %w", err) } else if len(templateList.DevPodWorkspaceTemplates) == 0 { return templateList, fmt.Errorf("seems like there is no template allowed in project %s, please make sure to at least have a single template available", projectName) } return templateList, nil } func FindTemplate(ctx context.Context, managementClient kube.Interface, projectName, templateName string) (*managementv1.DevPodWorkspaceTemplate, error) { templateList, err := managementClient.Loft().ManagementV1().Projects().ListTemplates(ctx, projectName, metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("list templates: %w", err) } else if len(templateList.DevPodWorkspaceTemplates) == 0 { return nil, fmt.Errorf("seems like there is no DevPod template allowed in project %s, please make sure to at least have a single template available", projectName) } // find template var template *managementv1.DevPodWorkspaceTemplate for _, workspaceTemplate := range templateList.DevPodWorkspaceTemplates { if workspaceTemplate.Name == templateName { t := workspaceTemplate template = &t break } } if template == nil { return nil, fmt.Errorf("couldn't find template %s", templateName) } return template, nil } func GetTemplateParameters(template *managementv1.DevPodWorkspaceTemplate, templateVersion string) ([]storagev1.AppParameter, error) { if templateVersion == "latest" { templateVersion = "" } if templateVersion == "" { latestVersion := GetLatestVersion(template) if latestVersion == nil { return nil, fmt.Errorf("couldn't find any version in template") } return latestVersion.(*storagev1.DevPodWorkspaceTemplateVersion).Parameters, nil } _, latestMatched, err := GetLatestMatchedVersion(template, templateVersion) if err != nil { return nil, err } else if latestMatched == nil { return nil, fmt.Errorf("couldn't find any matching version to %s", templateVersion) } return latestMatched.(*storagev1.DevPodWorkspaceTemplateVersion).Parameters, nil } type matchedVersion struct { Object storagev1.VersionAccessor Version semver.Version } func GetLatestVersion(versions storagev1.VersionsAccessor) storagev1.VersionAccessor { // find the latest version var latestVersion *matchedVersion for _, version := range versions.GetVersions() { parsedVersion, err := semver.Parse(strings.TrimPrefix(version.GetVersion(), "v")) if err != nil { continue } // latest available version if latestVersion == nil || latestVersion.Version.LT(parsedVersion) { latestVersion = &matchedVersion{ Object: version, Version: parsedVersion, } } } if latestVersion == nil { return nil } return latestVersion.Object } func GetLatestMatchedVersion(versions storagev1.VersionsAccessor, versionPattern string) (latestVersion storagev1.VersionAccessor, latestMatchedVersion storagev1.VersionAccessor, err error) { // parse version splittedVersion := strings.Split(strings.ToLower(strings.TrimPrefix(versionPattern, "v")), ".") if len(splittedVersion) != 3 { return nil, nil, fmt.Errorf("couldn't parse version %s, expected version in format: 0.0.0", versionPattern) } // find latest version that matches our defined version var latestVersionObj *matchedVersion var latestMatchedVersionObj *matchedVersion for _, version := range versions.GetVersions() { parsedVersion, err := semver.Parse(strings.TrimPrefix(version.GetVersion(), "v")) if err != nil { continue } // does the version match our restrictions? if (splittedVersion[0] == "x" || splittedVersion[0] == "X" || strconv.FormatUint(parsedVersion.Major, 10) == splittedVersion[0]) && (splittedVersion[1] == "x" || splittedVersion[1] == "X" || strconv.FormatUint(parsedVersion.Minor, 10) == splittedVersion[1]) && (splittedVersion[2] == "x" || splittedVersion[2] == "X" || strconv.FormatUint(parsedVersion.Patch, 10) == splittedVersion[2]) { if latestMatchedVersionObj == nil || latestMatchedVersionObj.Version.LT(parsedVersion) { latestMatchedVersionObj = &matchedVersion{ Object: version, Version: parsedVersion, } } } // latest available version if latestVersionObj == nil || latestVersionObj.Version.LT(parsedVersion) { latestVersionObj = &matchedVersion{ Object: version, Version: parsedVersion, } } } if latestVersionObj != nil { latestVersion = latestVersionObj.Object } if latestMatchedVersionObj != nil { latestMatchedVersion = latestMatchedVersionObj.Object } return latestVersion, latestMatchedVersion, nil } var replaceRegEx = regexp.MustCompile("[^a-zA-Z0-9]+") func VariableToEnvironmentVariable(variable string) string { return "TEMPLATE_OPTION_" + strings.ToUpper(replaceRegEx.ReplaceAllString(variable, "_")) } ================================================ FILE: cmd/pro/provider/list/workspaces.go ================================================ package list import ( "context" "encoding/json" "fmt" "os" managementv1 "github.com/loft-sh/api/v4/pkg/apis/management/v1" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/platform/labels" "github.com/loft-sh/devpod/pkg/platform/project" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // WorkspacesCmd holds the cmd flags type WorkspacesCmd struct { *flags.GlobalFlags log log.Logger } // NewWorkspacesCmd creates a new command func NewWorkspacesCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &WorkspacesCmd{ GlobalFlags: globalFlags, log: log.GetInstance(), } c := &cobra.Command{ Use: "workspaces", Short: "Lists workspaces for the provider", Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context()) }, } return c } func (cmd *WorkspacesCmd) Run(ctx context.Context) error { baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } managementClient, err := baseClient.Management() if err != nil { return err } projectList, err := managementClient.Loft().ManagementV1().Projects().List(ctx, metav1.ListOptions{}) if err != nil { return fmt.Errorf("list projects: %w", err) } else if len(projectList.Items) == 0 { return fmt.Errorf("you don't have access to any projects within DevPod Pro, please make sure you have at least access to 1 project") } filterByOwner := os.Getenv(provider.LOFT_FILTER_BY_OWNER) == "true" workspaces := []*managementv1.DevPodWorkspaceInstance{} for _, p := range projectList.Items { ns := project.ProjectNamespace(p.GetName()) workspaceList, err := managementClient.Loft().ManagementV1().DevPodWorkspaceInstances(ns).List(ctx, metav1.ListOptions{}) if err != nil { cmd.log.Info("list workspaces in project \"%s\": %w", p.GetName(), err) continue } for _, instance := range workspaceList.Items { instance := &instance if filterByOwner && !platform.IsOwner(baseClient.Self(), instance.GetOwner()) { continue } if instance.GetLabels() == nil { instance.Labels = map[string]string{} } instance.Labels[labels.ProjectLabel] = p.GetName() workspaces = append(workspaces, instance) } } wBytes, err := json.Marshal(workspaces) if err != nil { return fmt.Errorf("marshal workspaces: %w", err) } fmt.Println(string(wBytes)) return nil } ================================================ FILE: cmd/pro/provider/provider.go ================================================ package provider import ( "os" "github.com/loft-sh/devpod/cmd/agent" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/cmd/pro/provider/create" "github.com/loft-sh/devpod/cmd/pro/provider/get" "github.com/loft-sh/devpod/cmd/pro/provider/list" "github.com/loft-sh/devpod/cmd/pro/provider/update" "github.com/loft-sh/devpod/cmd/pro/provider/watch" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/telemetry" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // NewProProviderCmd creates a new cobra command func NewProProviderCmd(globalFlags *flags.GlobalFlags) *cobra.Command { c := &cobra.Command{ Use: "provider", Short: "DevPod Pro provider commands", Args: cobra.NoArgs, Hidden: true, PersistentPreRun: func(cmd *cobra.Command, args []string) { if (globalFlags.Config == "" || globalFlags.Config == client.DefaultCacheConfig) && os.Getenv("LOFT_CONFIG") != "" { globalFlags.Config = os.Getenv(platform.ConfigEnv) } log.Default.SetFormat(log.JSONFormat) if os.Getenv(clientimplementation.DevPodDebug) == "true" { globalFlags.Debug = true } // Disable debug hints if we execute pro commands from DevPod Desktop // We're reusing the agent.AgentExecutedAnnotation for simplicity, could rename in the future if os.Getenv(telemetry.UIEnvVar) == "true" { cmd.VisitParents(func(c *cobra.Command) { // find the root command if c.Name() == "devpod" { if c.Annotations == nil { c.Annotations = map[string]string{} } c.Annotations[agent.AgentExecutedAnnotation] = "true" } }) } }, } c.AddCommand(list.NewCmd(globalFlags)) c.AddCommand(watch.NewCmd(globalFlags)) c.AddCommand(create.NewCmd(globalFlags)) c.AddCommand(get.NewCmd(globalFlags)) c.AddCommand(update.NewCmd(globalFlags)) c.AddCommand(NewHealthCmd(globalFlags)) c.AddCommand(NewUpCmd(globalFlags)) c.AddCommand(NewStopCmd(globalFlags)) c.AddCommand(NewSshCmd(globalFlags)) c.AddCommand(NewStatusCmd(globalFlags)) c.AddCommand(NewDeleteCmd(globalFlags)) c.AddCommand(NewRebuildCmd(globalFlags)) return c } ================================================ FILE: cmd/pro/provider/rebuild.go ================================================ package provider import ( "context" "encoding/json" "fmt" "net/url" "os" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/platform/remotecommand" "github.com/loft-sh/log" "github.com/spf13/cobra" ) const AllWorkspaces = "all" // RebuildCmd holds the cmd flags type RebuildCmd struct { *flags.GlobalFlags Log log.Logger Project string } // NewRebuildCmd creates a new command func NewRebuildCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &RebuildCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "rebuild", Short: "Rebuild a workspace", RunE: func(cobraCmd *cobra.Command, args []string) error { log.Default.SetFormat(log.TextFormat) return cmd.Run(cobraCmd.Context(), args) }, } c.Flags().StringVar(&cmd.Project, "project", "", "The project to use") _ = c.MarkFlagRequired("project") return c } func (cmd *RebuildCmd) Run(ctx context.Context, args []string) error { if len(args) == 0 { return fmt.Errorf("please provide a workspace name") } targetWorkspace := args[0] baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } workspace, err := platform.FindInstanceByName(ctx, baseClient, targetWorkspace, cmd.Project) if err != nil { return err } opts := struct { Recreate bool `json:"recreate"` }{Recreate: true} rawOpts, err := json.Marshal(opts) if err != nil { return err } values := url.Values{"options": []string{string(rawOpts)}, "cliMode": []string{"true"}} conn, err := platform.DialInstance(baseClient, workspace, "up", values, cmd.Log) if err != nil { return err } _, err = remotecommand.ExecuteConn(ctx, conn, os.Stdin, os.Stdout, os.Stderr, cmd.Log.ErrorStreamOnly()) if err != nil { return fmt.Errorf("error executing: %w", err) } return nil } ================================================ FILE: cmd/pro/provider/ssh.go ================================================ package provider import ( "context" "fmt" "io" "os" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/platform/remotecommand" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // SshCmd holds the cmd flags type SshCmd struct { *flags.GlobalFlags Log log.Logger } // NewSshCmd creates a new command func NewSshCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &SshCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Hidden: true, Use: "ssh", Short: "Runs ssh on a workspace", Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), os.Stdin, os.Stdout, os.Stderr) }, } return c } func (cmd *SshCmd) Run(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } info, err := platform.GetWorkspaceInfoFromEnv() if err != nil { return err } workspace, err := platform.FindInstanceInProject(ctx, baseClient, info.UID, info.ProjectName) if err != nil { return err } else if workspace == nil { return fmt.Errorf("couldn't find workspace") } conn, err := platform.DialInstance(baseClient, workspace, "ssh", platform.OptionsFromEnv("DEVPOD_FLAGS_SSH"), cmd.Log) if err != nil { return err } _, err = remotecommand.ExecuteConn(ctx, conn, stdin, stdout, stderr, cmd.Log.ErrorStreamOnly()) if err != nil { return fmt.Errorf("error executing: %w", err) } return nil } ================================================ FILE: cmd/pro/provider/status.go ================================================ package provider import ( "context" "encoding/json" "fmt" "io" "os" storagev1 "github.com/loft-sh/api/v4/pkg/apis/storage/v1" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/platform/remotecommand" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // StatusCmd holds the cmd flags type StatusCmd struct { *flags.GlobalFlags Log log.Logger } // NewStatusCmd creates a new command func NewStatusCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &StatusCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Hidden: true, Use: "status", Short: "Runs status on a workspace", Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), os.Stdin, os.Stdout, os.Stderr) }, } return c } func (cmd *StatusCmd) Run(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } info, err := platform.GetWorkspaceInfoFromEnv() if err != nil { return err } workspace, err := platform.FindInstanceInProject(ctx, baseClient, info.UID, info.ProjectName) if err != nil { return err } else if workspace == nil { out, err := json.Marshal(&storagev1.WorkspaceStatusResult{ ID: os.Getenv(platform.WorkspaceIDEnv), Context: os.Getenv(platform.WorkspaceContextEnv), State: string(storagev1.WorkspaceStatusNotFound), Provider: os.Getenv(platform.WorkspaceProviderEnv), }) if err != nil { return err } fmt.Println(string(out)) return nil } conn, err := platform.DialInstance(baseClient, workspace, "getstatus", platform.OptionsFromEnv("DEVPOD_FLAGS_STATUS"), cmd.Log) if err != nil { return err } _, err = remotecommand.ExecuteConn(ctx, conn, stdin, stdout, stderr, cmd.Log.ErrorStreamOnly()) if err != nil { return fmt.Errorf("error executing: %w", err) } return nil } ================================================ FILE: cmd/pro/provider/stop.go ================================================ package provider import ( "context" "fmt" "io" "os" storagev1 "github.com/loft-sh/api/v4/pkg/apis/storage/v1" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/platform/remotecommand" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // StopCmd holds the cmd flags type StopCmd struct { *flags.GlobalFlags Log log.Logger } // NewStopCmd creates a new command func NewStopCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &StopCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Hidden: true, Use: "stop", Short: "Runs stop on a workspace", Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), os.Stdin, os.Stdout, os.Stderr) }, } return c } func (cmd *StopCmd) Run(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } info, err := platform.GetWorkspaceInfoFromEnv() if err != nil { return err } workspace, err := platform.FindInstanceInProject(ctx, baseClient, info.UID, info.ProjectName) if err != nil { return err } else if workspace == nil { return fmt.Errorf("couldn't find workspace") } conn, err := platform.DialInstance(baseClient, workspace, "stop", platform.OptionsFromEnv(storagev1.DevPodFlagsStop), cmd.Log) if err != nil { return err } _, err = remotecommand.ExecuteConn(ctx, conn, stdin, stdout, stderr, cmd.Log.ErrorStreamOnly()) if err != nil { return fmt.Errorf("error executing: %w", err) } return nil } ================================================ FILE: cmd/pro/provider/up.go ================================================ package provider import ( "context" "encoding/json" "fmt" "io" "os" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/platform/remotecommand" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" managementv1 "github.com/loft-sh/api/v4/pkg/apis/management/v1" storagev1 "github.com/loft-sh/api/v4/pkg/apis/storage/v1" corev1 "k8s.io/api/core/v1" ) // UpCmd holds the cmd flags: type UpCmd struct { *flags.GlobalFlags Log log.Logger streams streams } type streams struct { Stdin io.Reader Stdout io.Writer Stderr io.Writer } // NewUpCmd creates a new command func NewUpCmd(globalFlags *flags.GlobalFlags) *cobra.Command { logLevel := logrus.InfoLevel if os.Getenv(clientimplementation.DevPodDebug) == "true" || globalFlags.Debug { logLevel = logrus.DebugLevel } cmd := &UpCmd{ GlobalFlags: globalFlags, Log: log.NewStreamLoggerWithFormat( /* we don't use stdout */ nil, os.Stderr, logLevel, log.JSONFormat).ErrorStreamOnly(), streams: streams{ Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, }, } c := &cobra.Command{ Hidden: true, Use: "up", Short: "Runs up on a workspace", Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context()) }, } return c } func (cmd *UpCmd) Run(ctx context.Context) error { baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } info, err := platform.GetWorkspaceInfoFromEnv() if err != nil { return err } instance, err := platform.FindInstanceInProject(ctx, baseClient, info.UID, info.ProjectName) if err != nil { return err } else if instance == nil { return fmt.Errorf("workspace %s not found in project %s. Looks like it does not exist anymore and you can delete it", info.ID, info.ProjectName) } // Log current workspace information. This is both useful to the user to understand the workspace configuration // and to us when we receive troubleshooting logs printInstanceInfo(instance, cmd.Log) if instance.Spec.TemplateRef != nil && templateUpdateRequired(instance) { cmd.Log.Info("Template update required") oldInstance := instance.DeepCopy() instance.Spec.TemplateRef.SyncOnce = true instance, err = platform.UpdateInstance(ctx, baseClient, oldInstance, instance, cmd.Log) if err != nil { return fmt.Errorf("update instance: %w", err) } cmd.Log.Info("Successfully updated template") } return cmd.up(ctx, instance, baseClient) } func (cmd *UpCmd) up(ctx context.Context, workspace *managementv1.DevPodWorkspaceInstance, client client.Client) error { options := platform.OptionsFromEnv(storagev1.DevPodFlagsUp) if options != nil && os.Getenv("DEBUG") == "true" { options.Add("debug", "true") } conn, err := platform.DialInstance(client, workspace, "up", options, cmd.Log) if err != nil { return err } _, err = remotecommand.ExecuteConn(ctx, conn, cmd.streams.Stdin, cmd.streams.Stdout, cmd.streams.Stderr, cmd.Log) if err != nil { return fmt.Errorf("error executing: %w", err) } return nil } func templateUpdateRequired(instance *managementv1.DevPodWorkspaceInstance) bool { var templateResolved, templateChangesAvailable bool for _, condition := range instance.Status.Conditions { if condition.Type == storagev1.InstanceTemplateResolved { templateResolved = condition.Status == corev1.ConditionTrue continue } if condition.Type == storagev1.InstanceTemplateSynced { templateChangesAvailable = condition.Status == corev1.ConditionFalse && condition.Reason == "TemplateChangesAvailable" continue } } return !templateResolved || templateChangesAvailable } func printInstanceInfo(instance *managementv1.DevPodWorkspaceInstance, log log.Logger) { workspaceConfig, _ := json.Marshal(struct { // Cluster storagev1.WorkspaceTargetNamespace Template *storagev1.TemplateRef Parameters string }{ // Cluster: cluster, // FIXME: Bring back runner ref Template: instance.Spec.TemplateRef, Parameters: instance.Spec.Parameters, }) log.Debug("Starting pro workspace with configuration", string(workspaceConfig)) } ================================================ FILE: cmd/pro/provider/update/update.go ================================================ package update import ( "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/spf13/cobra" ) // NewCmd creates a new cobra command func NewCmd(globalFlags *flags.GlobalFlags) *cobra.Command { c := &cobra.Command{ Use: "update", Short: "DevPod Pro Provider update commands", Args: cobra.NoArgs, Hidden: true, } c.AddCommand(NewWorkspaceCmd(globalFlags)) return c } ================================================ FILE: cmd/pro/provider/update/workspace.go ================================================ package update import ( "context" "encoding/json" "fmt" "io" "os" managementv1 "github.com/loft-sh/api/v4/pkg/apis/management/v1" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/platform/form" "github.com/loft-sh/devpod/pkg/platform/project" "github.com/loft-sh/log" "github.com/loft-sh/log/terminal" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // WorkspaceCmd holds the cmd flags type WorkspaceCmd struct { *flags.GlobalFlags Log log.Logger } // NewWorkspaceCmd creates a new command func NewWorkspaceCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &WorkspaceCmd{ GlobalFlags: globalFlags, Log: log.GetInstance().ErrorStreamOnly(), } c := &cobra.Command{ Use: "workspace", Short: "Create a workspace", Hidden: true, Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), os.Stdin, os.Stdout, os.Stderr) }, } return c } func (cmd *WorkspaceCmd) Run(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } // GUI instanceEnv := os.Getenv(platform.WorkspaceInstanceEnv) if instanceEnv != "" { newInstance := &managementv1.DevPodWorkspaceInstance{} err := json.Unmarshal([]byte(instanceEnv), newInstance) if err != nil { return fmt.Errorf("unmarshal workpace instance %s: %w", instanceEnv, err) } newInstance.TypeMeta = metav1.TypeMeta{} // ignore projectName := project.ProjectFromNamespace(newInstance.GetNamespace()) oldInstance, err := platform.FindInstanceByName(ctx, baseClient, newInstance.GetName(), projectName) if err != nil { return err } updatedInstance, err := updateInstance(ctx, baseClient, oldInstance, newInstance, cmd.Log) if err != nil { return err } out, err := json.Marshal(updatedInstance) if err != nil { return err } fmt.Println(string(out)) return nil } // CLI if !terminal.IsTerminalIn { return fmt.Errorf("unable to update instance through CLI if stdin is not a terminal") } workspaceID := os.Getenv(platform.WorkspaceIDEnv) workspaceUID := os.Getenv(platform.WorkspaceUIDEnv) project := os.Getenv(platform.ProjectEnv) if workspaceUID == "" || workspaceID == "" || project == "" { return fmt.Errorf("workspaceID, workspaceUID or project not found: %s, %s, %s", workspaceID, workspaceUID, project) } oldInstance, err := platform.FindInstanceInProject(ctx, baseClient, workspaceUID, project) if err != nil { return err } newInstance, err := form.UpdateInstance(ctx, baseClient, oldInstance, cmd.Log) if err != nil { return err } _, err = updateInstance(ctx, baseClient, oldInstance, newInstance, cmd.Log) if err != nil { return err } return nil } func updateInstance(ctx context.Context, client client.Client, oldInstance *managementv1.DevPodWorkspaceInstance, newInstance *managementv1.DevPodWorkspaceInstance, log log.Logger) (*managementv1.DevPodWorkspaceInstance, error) { // This ensures the template is kept up to date with configuration changes if newInstance.Spec.TemplateRef != nil { newInstance.Spec.TemplateRef.SyncOnce = true } return platform.UpdateInstance(ctx, client, oldInstance, newInstance, log) } ================================================ FILE: cmd/pro/provider/watch/watch.go ================================================ package watch import ( "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/spf13/cobra" ) // NewCmd creates a new cobra command func NewCmd(globalFlags *flags.GlobalFlags) *cobra.Command { c := &cobra.Command{ Use: "watch", Short: "DevPod Pro Provider watch commands", Args: cobra.NoArgs, Hidden: true, } c.AddCommand(NewWorkspacesCmd(globalFlags)) return c } ================================================ FILE: cmd/pro/provider/watch/workspaces.go ================================================ package watch import ( "context" "encoding/json" "fmt" "io" "os" "sync" "time" managementv1 "github.com/loft-sh/api/v4/pkg/apis/management/v1" storagev1 "github.com/loft-sh/api/v4/pkg/apis/storage/v1" loftclient "github.com/loft-sh/api/v4/pkg/clientset/versioned" informers "github.com/loft-sh/api/v4/pkg/informers/externalversions" informermanagementv1 "github.com/loft-sh/api/v4/pkg/informers/externalversions/management/v1" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/platform/project" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/cache" ) // WorkspacesCmd holds the cmd flags type WorkspacesCmd struct { *flags.GlobalFlags Log log.Logger } // NewWorkspacesCmd creates a new command func NewWorkspacesCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &WorkspacesCmd{ GlobalFlags: globalFlags, Log: log.Default.ErrorStreamOnly(), } c := &cobra.Command{ Use: "workspaces", Short: "Watches all workspaces for a project", Hidden: true, Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), os.Stdin, os.Stdout, os.Stderr) }, } return c } type ProWorkspaceInstance struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec managementv1.DevPodWorkspaceInstanceSpec `json:"spec,omitempty"` Status ProWorkspaceInstanceStatus `json:"status,omitempty"` } type ProWorkspaceInstanceStatus struct { managementv1.DevPodWorkspaceInstanceStatus `json:",inline"` Source *provider.WorkspaceSource `json:"source,omitempty"` IDE *provider.WorkspaceIDEConfig `json:"ide,omitempty"` } func (cmd *WorkspacesCmd) Run(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { if cmd.Context == "" { cmd.Context = config.DefaultContext } projectName := os.Getenv(provider.LOFT_PROJECT) if projectName == "" { return fmt.Errorf("project name not found") } baseClient, err := client.InitClientFromPath(ctx, cmd.Config) if err != nil { return err } managementConfig, err := baseClient.ManagementConfig() if err != nil { return err } clientset, err := loftclient.NewForConfig(managementConfig) if err != nil { return err } factory := informers.NewSharedInformerFactoryWithOptions(clientset, time.Second*60, informers.WithNamespace(project.ProjectNamespace(projectName)), ) workspaceInformer := factory.Management().V1().DevPodWorkspaceInstances() self := baseClient.Self() filterByOwner := os.Getenv(provider.LOFT_FILTER_BY_OWNER) == "true" instanceStore := newStore(workspaceInformer, self, cmd.Context, filterByOwner, cmd.Log) _, err = workspaceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { instance, ok := obj.(*managementv1.DevPodWorkspaceInstance) if !ok { return } instanceStore.Add(instance) printInstances(stdout, instanceStore.List()) }, UpdateFunc: func(oldObj interface{}, newObj interface{}) { oldInstance, ok := oldObj.(*managementv1.DevPodWorkspaceInstance) if !ok { return } newInstance, ok := newObj.(*managementv1.DevPodWorkspaceInstance) if !ok { return } instanceStore.Update(oldInstance, newInstance) printInstances(stdout, instanceStore.List()) }, DeleteFunc: func(obj interface{}) { instance, ok := obj.(*managementv1.DevPodWorkspaceInstance) if !ok { // check for DeletedFinalStateUnknown. Can happen if the informer misses the delete event u, ok := obj.(cache.DeletedFinalStateUnknown) if !ok { return } instance, ok = u.Obj.(*managementv1.DevPodWorkspaceInstance) if !ok { return } } instanceStore.Delete(instance) printInstances(stdout, instanceStore.List()) }, }) if err != nil { return err } stopCh := make(chan struct{}) defer close(stopCh) go func() { factory.Start(stopCh) factory.WaitForCacheSync(stopCh) // Kick off initial message printInstances(stdout, instanceStore.List()) }() <-stopCh return nil } type instanceStore struct { informer informermanagementv1.DevPodWorkspaceInstanceInformer self *managementv1.Self context string filterByOwner bool m sync.Mutex instances map[string]*ProWorkspaceInstance log log.Logger } func newStore(informer informermanagementv1.DevPodWorkspaceInstanceInformer, self *managementv1.Self, context string, filterByOwner bool, log log.Logger) *instanceStore { return &instanceStore{ informer: informer, self: self, context: context, filterByOwner: filterByOwner, instances: map[string]*ProWorkspaceInstance{}, log: log, } } func (s *instanceStore) key(meta metav1.ObjectMeta) string { return fmt.Sprintf("%s/%s", meta.Namespace, meta.Name) } func (s *instanceStore) Add(instance *managementv1.DevPodWorkspaceInstance) { if s.filterByOwner && !platform.IsOwner(s.self, instance.Spec.Owner) { return } var source *provider.WorkspaceSource if instance.GetAnnotations() != nil && instance.GetAnnotations()[storagev1.DevPodWorkspaceSourceAnnotation] != "" { source = provider.ParseWorkspaceSource(instance.GetAnnotations()[storagev1.DevPodWorkspaceSourceAnnotation]) } var ideConfig *provider.WorkspaceIDEConfig if instance.GetLabels() != nil && instance.GetLabels()[storagev1.DevPodWorkspaceIDLabel] != "" { id := instance.GetLabels()[storagev1.DevPodWorkspaceIDLabel] workspaceConfig, err := provider.LoadWorkspaceConfig(s.context, id) if err == nil { ideConfig = &workspaceConfig.IDE } } proInstance := &ProWorkspaceInstance{ TypeMeta: instance.TypeMeta, ObjectMeta: instance.ObjectMeta, Spec: instance.Spec, Status: ProWorkspaceInstanceStatus{ DevPodWorkspaceInstanceStatus: instance.Status, Source: source, IDE: ideConfig, }, } key := s.key(instance.ObjectMeta) s.m.Lock() s.instances[key] = proInstance s.m.Unlock() } func (s *instanceStore) Update(oldInstance *managementv1.DevPodWorkspaceInstance, newInstance *managementv1.DevPodWorkspaceInstance) { s.Add(newInstance) } func (s *instanceStore) Delete(instance *managementv1.DevPodWorkspaceInstance) { if s.filterByOwner && !platform.IsOwner(s.self, instance.Spec.Owner) { return } s.m.Lock() defer s.m.Unlock() key := s.key(instance.ObjectMeta) delete(s.instances, key) } func (s *instanceStore) List() []*ProWorkspaceInstance { instanceList := []*ProWorkspaceInstance{} // Check local imported workspaces // Eventually this should be implemented by filtering based on ownership and access on the CRD, for now we're stuck with this approach... localWorkspaces, err := workspace.ListLocalWorkspaces(s.context, false, s.log) if err == nil { for _, workspace := range localWorkspaces { if workspace.Imported && workspace.Pro != nil { // get instance for imported workspace selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ MatchLabels: map[string]string{ storagev1.DevPodWorkspaceUIDLabel: workspace.UID, }, }) if err != nil { continue } l, err := s.informer.Lister(). DevPodWorkspaceInstances(project.ProjectFromNamespace(workspace.Pro.Project)). List(selector) if err != nil { continue } if len(l) == 0 { continue } instance := l[0] s.m.Lock() if _, ok := s.instances[s.key(instance.ObjectMeta)]; ok { continue } s.m.Unlock() var source *provider.WorkspaceSource if instance.GetAnnotations() != nil && instance.GetAnnotations()[storagev1.DevPodWorkspaceSourceAnnotation] != "" { source = provider.ParseWorkspaceSource(instance.GetAnnotations()[storagev1.DevPodWorkspaceSourceAnnotation]) } var ideConfig *provider.WorkspaceIDEConfig if instance.GetLabels() != nil && instance.GetLabels()[storagev1.DevPodWorkspaceIDLabel] != "" { id := instance.GetLabels()[storagev1.DevPodWorkspaceIDLabel] workspaceConfig, err := provider.LoadWorkspaceConfig(s.context, id) if err == nil { ideConfig = &workspaceConfig.IDE } } proInstance := &ProWorkspaceInstance{ TypeMeta: instance.TypeMeta, ObjectMeta: instance.ObjectMeta, Spec: instance.Spec, Status: ProWorkspaceInstanceStatus{ DevPodWorkspaceInstanceStatus: instance.Status, Source: source, IDE: ideConfig, }, } instanceList = append(instanceList, proInstance) } } } s.m.Lock() for _, instance := range s.instances { instanceList = append(instanceList, instance) } s.m.Unlock() return instanceList } func printInstances(w io.Writer, instances []*ProWorkspaceInstance) { out, err := json.Marshal(instances) if err != nil { return } fmt.Fprintln(w, string(out)) } ================================================ FILE: cmd/pro/rebuild.go ================================================ package pro import ( "context" "encoding/json" "fmt" "net/url" "os" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/remotecommand" "github.com/loft-sh/log" "github.com/spf13/cobra" ) const AllWorkspaces = "all" // RebuildCmd holds the cmd flags type RebuildCmd struct { *flags.GlobalFlags Log log.Logger Project string Host string } // NewRebuildCmd creates a new command func NewRebuildCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &RebuildCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "rebuild", Short: "Rebuild a workspace", RunE: func(cobraCmd *cobra.Command, args []string) error { log.Default.SetFormat(log.TextFormat) return cmd.Run(cobraCmd.Context(), args) }, } c.Flags().StringVar(&cmd.Project, "project", "", "The project to use") _ = c.MarkFlagRequired("project") c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") return c } func (cmd *RebuildCmd) Run(ctx context.Context, args []string) error { if len(args) == 0 { return fmt.Errorf("please provide a workspace name") } targetWorkspace := args[0] devPodConfig, err := config.LoadConfig(cmd.Context, "") if err != nil { return err } baseClient, err := platform.InitClientFromHost(ctx, devPodConfig, cmd.Host, cmd.Log) if err != nil { return fmt.Errorf("resolve host \"%s\": %w", cmd.Host, err) } workspace, err := platform.FindInstanceByName(ctx, baseClient, targetWorkspace, cmd.Project) if err != nil { return err } opts := struct { Recreate bool `json:"recreate"` }{Recreate: true} rawOpts, err := json.Marshal(opts) if err != nil { return err } values := url.Values{"options": []string{string(rawOpts)}, "cliMode": []string{"true"}} conn, err := platform.DialInstance(baseClient, workspace, "up", values, cmd.Log) if err != nil { return err } _, err = remotecommand.ExecuteConn(ctx, conn, os.Stdin, os.Stdout, os.Stderr, cmd.Log.ErrorStreamOnly()) if err != nil { return fmt.Errorf("error executing: %w", err) } return nil } ================================================ FILE: cmd/pro/reset/password.go ================================================ package reset import ( "context" "crypto/sha256" "fmt" "strings" storagev1 "github.com/loft-sh/api/v4/pkg/apis/storage/v1" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform/kube" "github.com/loft-sh/devpod/pkg/random" "github.com/loft-sh/log" "github.com/loft-sh/log/survey" "github.com/pkg/errors" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" ) // PasswordCmd holds the lags type PasswordCmd struct { *flags.GlobalFlags User string Password string Create bool Force bool Log log.Logger } // NewPasswordCmd creates a new command func NewPasswordCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &PasswordCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } description := ` Resets the password of a user. Example: devpod pro reset password devpod pro reset password --user admin ####################################################### ` c := &cobra.Command{ Use: "password", Short: "Resets the password of a user", Long: description, Args: cobra.NoArgs, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run() }, } c.Flags().StringVar(&cmd.User, "user", "admin", "The name of the user to reset the password") c.Flags().StringVar(&cmd.Password, "password", "", "The new password to use") c.Flags().BoolVar(&cmd.Create, "create", false, "Creates the user if it does not exist") c.Flags().BoolVar(&cmd.Force, "force", false, "If user had no password will create one") return c } // Run executes the functionality func (cmd *PasswordCmd) Run() error { restConfig, err := ctrl.GetConfig() if err != nil { return errors.Wrap(err, "get kube config") } managementClient, err := kube.NewForConfig(restConfig) if err != nil { return err } // get user cmd.Log.Infof("Resetting password of user %s", cmd.User) user, err := managementClient.Loft().StorageV1().Users().Get(context.Background(), cmd.User, metav1.GetOptions{}) if err != nil && !kerrors.IsNotFound(err) { return errors.Wrap(err, "get user") } else if kerrors.IsNotFound(err) { // create user if !cmd.Create { return fmt.Errorf("user %s was not found, run with '--create' to create this user automatically", cmd.User) } user, err = managementClient.Loft().StorageV1().Users().Create(context.Background(), &storagev1.User{ ObjectMeta: metav1.ObjectMeta{ Name: cmd.User, }, Spec: storagev1.UserSpec{ Username: cmd.User, Subject: cmd.User, Groups: []string{ "system:masters", }, PasswordRef: &storagev1.SecretRef{ SecretName: "loft-password-" + random.String(5), SecretNamespace: "loft", Key: "password", }, }, }, metav1.CreateOptions{}) if err != nil { return err } } // check if user had a password before if user.Spec.PasswordRef == nil || user.Spec.PasswordRef.SecretName == "" || user.Spec.PasswordRef.SecretNamespace == "" || user.Spec.PasswordRef.Key == "" { if !cmd.Force { return fmt.Errorf("user %s had no password. If you want to force password creation, please run with the '--force' flag", cmd.User) } user.Spec.PasswordRef = &storagev1.SecretRef{ SecretName: "loft-password-" + random.String(5), SecretNamespace: "loft", Key: "password", } user, err = managementClient.Loft().StorageV1().Users().Update(context.Background(), user, metav1.UpdateOptions{}) if err != nil { return errors.Wrap(err, "update user") } } // now ask user for new password password := cmd.Password if password == "" { for { password, err = cmd.Log.Question(&survey.QuestionOptions{ Question: "Please enter a new password", IsPassword: true, }) password = strings.TrimSpace(password) if err != nil { return err } else if password == "" { cmd.Log.Error("Please enter a password") continue } break } } passwordHash := []byte(fmt.Sprintf("%x", sha256.Sum256([]byte(password)))) // check if secret exists passwordSecret, err := managementClient.CoreV1().Secrets(user.Spec.PasswordRef.SecretNamespace).Get(context.Background(), user.Spec.PasswordRef.SecretName, metav1.GetOptions{}) if err != nil && !kerrors.IsNotFound(err) { return err } else if kerrors.IsNotFound(err) { _, err = managementClient.CoreV1().Secrets(user.Spec.PasswordRef.SecretNamespace).Create(context.Background(), &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: user.Spec.PasswordRef.SecretName, Namespace: user.Spec.PasswordRef.SecretNamespace, }, Data: map[string][]byte{ user.Spec.PasswordRef.Key: passwordHash, }, }, metav1.CreateOptions{}) if err != nil { return errors.Wrap(err, "create password secret") } } else { if passwordSecret.Data == nil { passwordSecret.Data = map[string][]byte{} } passwordSecret.Data[user.Spec.PasswordRef.Key] = passwordHash _, err = managementClient.CoreV1().Secrets(user.Spec.PasswordRef.SecretNamespace).Update(context.Background(), passwordSecret, metav1.UpdateOptions{}) if err != nil { return errors.Wrap(err, "update password secret") } } cmd.Log.Donef("Successfully reset password of user %s", cmd.User) return nil } ================================================ FILE: cmd/pro/reset/reset.go ================================================ package reset import ( "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/spf13/cobra" ) // NewResetCmd creates a new cobra command func NewResetCmd(globalFlags *flags.GlobalFlags) *cobra.Command { c := &cobra.Command{ Use: "reset", Short: "Reset configuration", Args: cobra.NoArgs, } c.AddCommand(NewPasswordCmd(globalFlags)) return c } ================================================ FILE: cmd/pro/self.go ================================================ package pro import ( "bytes" "context" "fmt" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // SelfCmd holds the cmd flags type SelfCmd struct { *flags.GlobalFlags Log log.Logger Host string } // NewSelfCmd creates a new command func NewSelfCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &SelfCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "self", Short: "Get self", Hidden: true, RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") return c } func (cmd *SelfCmd) Run(ctx context.Context, devPodConfig *config.Config, provider *provider.ProviderConfig) error { var buf bytes.Buffer // ignore --debug because we tunnel json through stdio cmd.Log.SetLevel(logrus.InfoLevel) err := clientimplementation.RunCommandWithBinaries( ctx, "getSelf", provider.Exec.Proxy.Get.Self, devPodConfig.DefaultContext, nil, nil, devPodConfig.ProviderOptions(provider.Name), provider, nil, nil, &buf, nil, cmd.Log) if err != nil { return fmt.Errorf("get self: %w", err) } fmt.Println(buf.String()) return nil } ================================================ FILE: cmd/pro/sleep.go ================================================ package pro import ( "context" "fmt" "strconv" "time" clusterv1 "github.com/loft-sh/agentapi/v4/pkg/apis/loft/cluster/v1" storagev1 "github.com/loft-sh/api/v4/pkg/apis/storage/v1" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/project" "github.com/loft-sh/log" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" ) // SleepCmd holds the cmd flags type SleepCmd struct { *flags.GlobalFlags Log log.Logger Project string Host string ForceDuration int64 } // NewSleepCmd creates a new command func NewSleepCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &SleepCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "sleep", Short: "Put a workspace to sleep", RunE: func(cobraCmd *cobra.Command, args []string) error { log.Default.SetFormat(log.TextFormat) return cmd.Run(cobraCmd.Context(), args) }, } c.Flags().StringVar(&cmd.Project, "project", "", "The project to use") c.Flags().Int64Var(&cmd.ForceDuration, "prevent-wakeup", -1, "The amount of seconds this workspace should sleep until it can be woken up again (use 0 for infinite sleeping). During this time the space can only be woken up by `devpod pro wakeup`, manually deleting the annotation on the namespace or through the UI") _ = c.MarkFlagRequired("project") c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") return c } func (cmd *SleepCmd) Run(ctx context.Context, args []string) error { if len(args) == 0 { return fmt.Errorf("please provide a workspace name") } targetWorkspace := args[0] devPodConfig, err := config.LoadConfig(cmd.Context, "") if err != nil { return err } baseClient, err := platform.InitClientFromHost(ctx, devPodConfig, cmd.Host, cmd.Log) if err != nil { return err } workspaceInstance, err := platform.FindInstanceByName(ctx, baseClient, targetWorkspace, cmd.Project) if err != nil { return err } managementClient, err := baseClient.Management() if err != nil { return err } // create a deep copy of the workspace instance oldWorkspaceInstance := workspaceInstance.DeepCopy() oldWorkspaceInstance.Status = workspaceInstance.Status oldWorkspaceInstance.ObjectMeta = workspaceInstance.ObjectMeta oldWorkspaceInstance.TypeMeta = workspaceInstance.TypeMeta // create a patch from the old workspace instance patch := ctrlclient.MergeFrom(oldWorkspaceInstance) if workspaceInstance.Annotations == nil { workspaceInstance.Annotations = map[string]string{} } workspaceInstance.Annotations[clusterv1.SleepModeForceAnnotation] = "true" if cmd.ForceDuration >= 0 { workspaceInstance.Annotations[clusterv1.SleepModeForceDurationAnnotation] = strconv.FormatInt(cmd.ForceDuration, 10) } patchData, err := patch.Data(workspaceInstance) if err != nil { return fmt.Errorf("create patch: %w", err) } _, err = managementClient.Loft().ManagementV1().DevPodWorkspaceInstances(project.ProjectNamespace(cmd.Project)).Patch(ctx, workspaceInstance.Name, patch.Type(), patchData, metav1.PatchOptions{}) if err != nil { return err } // wait for sleeping cmd.Log.Info("Wait until workspace is sleeping...") err = wait.PollUntilContextTimeout(ctx, time.Second, platform.Timeout(), false, func(ctx context.Context) (done bool, err error) { workspaceInstance, err := managementClient.Loft().ManagementV1().DevPodWorkspaceInstances(project.ProjectNamespace(cmd.Project)).Get(ctx, workspaceInstance.Name, metav1.GetOptions{}) if err != nil { return false, err } return workspaceInstance.Status.Phase == storagev1.InstanceSleeping, nil }) if err != nil { return fmt.Errorf("error waiting for workspace to start sleeping: %w", err) } cmd.Log.Donef("Successfully put workspace %s to sleep", workspaceInstance.Name) return nil } ================================================ FILE: cmd/pro/start.go ================================================ package pro import ( "bytes" "context" "crypto/hmac" "crypto/sha256" "crypto/tls" "encoding/json" "fmt" "io" "net/http" netUrl "net/url" "os" "os/exec" "path" "path/filepath" "strings" "time" "github.com/denisbrodbeck/machineid" jsonpatch "github.com/evanphx/json-patch" "github.com/mgutz/ansi" "github.com/skratchdot/open-golang/open" storagev1 "github.com/loft-sh/api/v4/pkg/apis/storage/v1" "github.com/loft-sh/api/v4/pkg/auth" loftclientset "github.com/loft-sh/api/v4/pkg/clientset/versioned" proflags "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/client" "github.com/loft-sh/devpod/pkg/util" "github.com/loft-sh/log" "github.com/loft-sh/log/hash" "github.com/loft-sh/log/scanner" "github.com/loft-sh/log/survey" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset" "k8s.io/kubectl/pkg/util/term" ) const LoftRouterDomainSecret = "loft-router-domain" const passwordChangedHint = "(has been changed)" const defaultUser = "admin" const defaultReleaseName = "devpod-pro" var defaultDeploymentName = "loft" // Need to update helm chart if we change this! // StartCmd holds the login cmd flags type StartCmd struct { proflags.GlobalFlags KubeClient kubernetes.Interface Log log.Logger RestConfig *rest.Config Context string Values string LocalPort string Version string DockerImage string Namespace string Password string Host string Email string ChartRepo string Product string ChartName string ChartPath string DockerArgs []string Reset bool NoPortForwarding bool NoTunnel bool NoLogin bool NoWait bool Upgrade bool ReuseValues bool Docker bool } // NewStartCmd creates a new command func NewStartCmd(flags *proflags.GlobalFlags) *cobra.Command { cmd := &StartCmd{GlobalFlags: *flags, Product: "devpod-pro", ChartName: "devpod-pro", Log: log.Default, } startCmd := &cobra.Command{ Use: "start", Short: "Start a Devpod Pro instance", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background()) }, } startCmd.Flags().BoolVar(&cmd.Docker, "docker", false, "If enabled will try to deploy DevPod Pro to the local docker installation.") startCmd.Flags().StringVar(&cmd.DockerImage, "docker-image", "", "The docker image to install.") startCmd.Flags().StringArrayVar(&cmd.DockerArgs, "docker-arg", []string{}, "Extra docker args") startCmd.Flags().StringVar(&cmd.Context, "context", "", "The kube context to use for installation") startCmd.Flags().StringVar(&cmd.Namespace, "namespace", "devpod-pro", "The namespace to install into") startCmd.Flags().StringVar(&cmd.Host, "host", "", "Provide a hostname to enable ingress and configure its hostname") startCmd.Flags().StringVar(&cmd.Password, "password", "", "The password to use for the admin account. (If empty this will be the namespace UID)") startCmd.Flags().StringVar(&cmd.Version, "version", "", "The version to install") startCmd.Flags().StringVar(&cmd.Values, "values", "", "Path to a file for extra helm chart values") startCmd.Flags().BoolVar(&cmd.ReuseValues, "reuse-values", true, "Reuse previous helm values on upgrade") startCmd.Flags().BoolVar(&cmd.Upgrade, "upgrade", false, "If true, will try to upgrade the release") startCmd.Flags().StringVar(&cmd.Email, "email", "", "The email to use for the installation") startCmd.Flags().BoolVar(&cmd.Reset, "reset", false, "If true, an existing instance will be deleted before installing DevPod Pro") startCmd.Flags().BoolVar(&cmd.NoWait, "no-wait", false, "If true, will not wait after installing it") startCmd.Flags().BoolVar(&cmd.NoTunnel, "no-tunnel", false, "If true, will not create a loft.host tunnel for this installation") startCmd.Flags().BoolVar(&cmd.NoLogin, "no-login", false, "If true, will not login to a DevPod Pro instance on start") startCmd.Flags().StringVar(&cmd.ChartPath, "chart-path", "", "The local chart path to deploy DevPod Pro") startCmd.Flags().StringVar(&cmd.ChartRepo, "chart-repo", "https://charts.loft.sh/", "The chart repo to deploy DevPod Pro") return startCmd } // Run runs the command logic func (cmd *StartCmd) Run(ctx context.Context) error { if cmd.Docker { return cmd.startDocker(ctx) } // only set local port by default in kubernetes installation if cmd.LocalPort == "" { cmd.LocalPort = "9898" } err := cmd.prepare() if err != nil { return err } cmd.Log.WriteString(logrus.InfoLevel, "\n") // Uninstall already existing instance if cmd.Reset { err = uninstall(ctx, cmd.KubeClient, cmd.RestConfig, cmd.Context, cmd.Namespace, cmd.Log) if err != nil { return err } } // Is already installed? isInstalled, err := isAlreadyInstalled(ctx, cmd.KubeClient, cmd.Namespace) if err != nil { return err } // Use default password if none is set if cmd.Password == "" { defaultPassword, err := getDefaultPassword(ctx, cmd.KubeClient, cmd.Namespace) if err != nil { return err } cmd.Password = defaultPassword } // Upgrade Loft if already installed if isInstalled { return cmd.handleAlreadyExistingInstallation(ctx) } // Install Loft cmd.Log.Info("Welcome to DevPod Pro!") cmd.Log.Info("This installer will help you to get started.") // make sure we are ready for installing err = cmd.prepareInstall(ctx) if err != nil { return err } err = cmd.upgrade() if err != nil { return err } return cmd.success(ctx) } func (cmd *StartCmd) upgrade() error { extraArgs := []string{} if cmd.Host != "" || cmd.NoTunnel { extraArgs = append(extraArgs, "--set-string", "env.DISABLE_LOFT_ROUTER=true") } if cmd.Password != "" { extraArgs = append(extraArgs, "--set", "admin.password="+cmd.Password) } if cmd.Host != "" { extraArgs = append(extraArgs, "--set", "ingress.enabled=true", "--set", "ingress.host="+cmd.Host) extraArgs = append(extraArgs, "--set", "env.LOFT_HOST="+cmd.Host) extraArgs = append(extraArgs, "--set", "devpodIngress.enabled=true", "--set", "devpodIngress.host=*."+cmd.Host) extraArgs = append(extraArgs, "--set", "env.DEVPOD_SUBDOMAIN=*."+cmd.Host) } if cmd.Version != "" { extraArgs = append(extraArgs, "--version", cmd.Version) } if cmd.Product != "" { extraArgs = append(extraArgs, "--set", "product="+cmd.Product) } // Do not use --reuse-values if --reset flag is provided because this should be a new install and it will cause issues with `helm template` if !cmd.Reset && cmd.ReuseValues { extraArgs = append(extraArgs, "--reuse-values") } if cmd.Values != "" { absValuesPath, err := filepath.Abs(cmd.Values) if err != nil { return err } extraArgs = append(extraArgs, "--values", absValuesPath) } chartName := cmd.ChartPath chartRepo := "" if chartName == "" { chartName = cmd.ChartName chartRepo = cmd.ChartRepo } err := upgradeRelease(chartName, chartRepo, cmd.Context, cmd.Namespace, extraArgs, cmd.Log) if err != nil { if !cmd.Reset { return errors.New(err.Error() + fmt.Sprintf("\n\nIf want to purge and reinstall DevPod Pro, run: %s\n", ansi.Color("devpod pro start --reset", "green+b"))) } // Try to purge Loft and retry install cmd.Log.Info("Trying to delete objects blocking current installation") manifests, err := getReleaseManifests(chartName, chartRepo, cmd.Context, cmd.Namespace, extraArgs, cmd.Log) if err != nil { return err } kubectlDelete := exec.Command("kubectl", "delete", "-f", "-", "--ignore-not-found=true", "--grace-period=0", "--force") buffer := bytes.Buffer{} buffer.Write([]byte(manifests)) kubectlDelete.Stdin = &buffer kubectlDelete.Stdout = os.Stdout kubectlDelete.Stderr = os.Stderr // Ignoring potential errors here _ = kubectlDelete.Run() // Retry Loft installation err = upgradeRelease(chartName, chartRepo, cmd.Context, cmd.Namespace, extraArgs, cmd.Log) if err != nil { return errors.New(err.Error() + fmt.Sprintf("\n\nExisting installation failed. Reach out to get help:\n- via Slack: %s (fastest option)\n- via Online Chat: %s\n- via Email: %s\n", ansi.Color("https://slack.loft.sh/", "green+b"), ansi.Color("https://loft.sh/", "green+b"), ansi.Color("support@loft.sh", "green+b"))) } } return nil } func (cmd *StartCmd) success(ctx context.Context) error { if cmd.NoWait { return nil } // wait until deployment is ready loftPod, err := cmd.waitForDeployment(ctx) if err != nil { return err } // check if installed locally isLocal := isInstalledLocally(ctx, cmd.KubeClient, cmd.Namespace) if isLocal { // check if loft domain secret is there if !cmd.NoTunnel { loftRouterDomain, err := cmd.pingLoftRouter(ctx, loftPod) if err != nil { cmd.Log.Errorf("Error retrieving loft router domain: %v", err) cmd.Log.Info("Fallback to use port-forwarding") } else if loftRouterDomain != "" { return cmd.successLoftRouter(loftRouterDomain) } } return cmd.successLocal() } // get login link cmd.Log.Info("Checking Loft status...") host, err := getIngressHost(ctx, cmd.KubeClient, cmd.Namespace) if err != nil { return err } // check if loft is reachable reachable, err := isHostReachable(ctx, host) if !reachable || err != nil { const ( YesOption = "Yes" NoOption = "No, please re-run the DNS check" ) answer, err := cmd.Log.Question(&survey.QuestionOptions{ Question: "Unable to reach Loft at https://" + host + ". Do you want to start port-forwarding instead?", DefaultValue: YesOption, Options: []string{ YesOption, NoOption, }, }) if err != nil { return err } if answer == YesOption { return cmd.successLocal() } } return cmd.successRemote(ctx, host) } func (cmd *StartCmd) successRemote(ctx context.Context, host string) error { printSuccess := func() { url := "https://" + host password := cmd.Password if password == "" { password = passwordChangedHint } cmd.Log.WriteString(logrus.InfoLevel, fmt.Sprintf(` ########################## LOGIN ############################ Username: `+ansi.Color("admin", "green+b")+` Password: `+ansi.Color(password, "green+b")+` # Change via UI or via: `+ansi.Color("devpod pro reset password", "green+b")+` Login via UI: %s Login via CLI: %s !!! You must accept the untrusted certificate in your browser !!! Follow this guide to add a valid certificate: %s ################################################################# DevPod Pro was successfully installed and can now be reached at: %s Thanks for using DevPod Pro! `, ansi.Color(url, "green+b"), ansi.Color("devpod pro login "+url, "green+b"), "https://loft.sh/docs/administration/ssl", url)) } ready, err := isHostReachable(ctx, host) if err != nil { return err } else if ready { printSuccess() return nil } // Print DNS Configuration cmd.Log.WriteString(logrus.InfoLevel, ` ################################### DNS CONFIGURATION REQUIRED ################################## Create a DNS A-record for `+host+` with the EXTERNAL-IP of your nginx-ingress controller. To find this EXTERNAL-IP, run the following command and look at the output: > kubectl get services -n ingress-nginx |---------------| NAME TYPE CLUSTER-IP | EXTERNAL-IP | PORT(S) AGE ingress-nginx-controller LoadBalancer 10.0.0.244 | XX.XXX.XXX.XX | 80:30984/TCP,443:31758/TCP 19m |^^^^^^^^^^^^^^^| EXTERNAL-IP may be 'pending' for a while until your cloud provider has created a new load balancer. ######################################################################################################### The command will wait until DevPod Pro is reachable under the host. `) cmd.Log.Info("Waiting for you to configure DNS, so DevPod Pro can be reached on https://" + host) err = wait.PollUntilContextTimeout(ctx, 5*time.Second, platform.Timeout(), true, func(ctx context.Context) (done bool, err error) { return isHostReachable(ctx, host) }) if err != nil { return err } cmd.Log.Done("DevPod Pro is reachable at https://" + host) printSuccess() return nil } func (cmd *StartCmd) successLocal() error { url := "https://localhost:" + cmd.LocalPort if !cmd.NoLogin { err := cmd.login(url) if err != nil { return err } } password := cmd.Password if password == "" { password = passwordChangedHint } cmd.Log.WriteString(logrus.InfoLevel, fmt.Sprintf(` ########################## LOGIN ############################ Username: `+ansi.Color("admin", "green+b")+` Password: `+ansi.Color(password, "green+b")+` # Change via UI or via: `+ansi.Color("devpod pro reset password", "green+b")+` Login via UI: %s Login via CLI: %s !!! You must accept the untrusted certificate in your browser !!! ################################################################# DevPod Pro was successfully installed. Thanks for using DevPod Pro! `, ansi.Color(url, "green+b"), ansi.Color("devpod pro login"+" --insecure "+url, "green+b"))) blockChan := make(chan bool) <-blockChan return nil } func (cmd *StartCmd) startDocker(ctx context.Context) error { cmd.Log.Infof("Starting DevPod Pro in Docker...") name := "devpod-pro" // prepare installation err := cmd.prepareDocker() if err != nil { return err } // try to find loft container containerID, err := cmd.findLoftContainer(ctx, name, true) if err != nil { return err } // check if container is there if containerID != "" && (cmd.Reset || cmd.Upgrade) { cmd.Log.Info("Existing instance found.") err = cmd.uninstallDocker(ctx, containerID) if err != nil { return err } containerID = "" } // Use default password if none is set if cmd.Password == "" { cmd.Password = getMachineUID(cmd.Log) } // check if is installed if containerID != "" { cmd.Log.Info("Existing instance found. Run with --upgrade to apply new configuration") return cmd.successDocker(ctx, containerID) } // Install Loft cmd.Log.Info("Welcome to DevPod Pro!") cmd.Log.Info("This installer will help you get started.") // make sure we are ready for installing containerID, err = cmd.runInDocker(ctx, name) if err != nil { return err } else if containerID == "" { return fmt.Errorf("%w: %s", ErrMissingContainer, "couldn't find Loft container after starting it") } return cmd.successDocker(ctx, containerID) } func (cmd *StartCmd) successDocker(ctx context.Context, containerID string) error { if cmd.NoWait { return nil } // wait until Loft is ready host, err := cmd.waitForLoftDocker(ctx, containerID) if err != nil { return err } // wait for domain to become reachable cmd.Log.Infof("Wait for DevPod Pro to become available at %s...", host) err = wait.PollUntilContextTimeout(ctx, time.Second, time.Minute*10, true, func(ctx context.Context) (bool, error) { containerDetails, err := cmd.inspectContainer(ctx, containerID) if err != nil { return false, fmt.Errorf("inspect loft container: %w", err) } else if strings.ToLower(containerDetails.State.Status) == "exited" || strings.ToLower(containerDetails.State.Status) == "dead" { logs, _ := cmd.logsContainer(ctx, containerID) return false, fmt.Errorf("container failed (status: %s):\n %s", containerDetails.State.Status, logs) } return isHostReachable(ctx, host) }) if err != nil { return fmt.Errorf("error waiting for DevPod Pro: %w", err) } // print success message PrintSuccessMessageDockerInstall(host, cmd.Password, cmd.Log) return nil } func PrintSuccessMessageDockerInstall(host, password string, log log.Logger) { url := "https://" + host log.WriteString(logrus.InfoLevel, fmt.Sprintf(` ########################## LOGIN ############################ Username: `+ansi.Color("admin", "green+b")+` Password: `+ansi.Color(password, "green+b")+` Login via UI: %s Login via CLI: %s ################################################################# DevPod Pro was successfully installed and can now be reached at: %s Thanks for using DevPod Pro! `, ansi.Color(url, "green+b"), ansi.Color("devpod pro login"+" "+url, "green+b"), url, )) } func (cmd *StartCmd) waitForLoftDocker(ctx context.Context, containerID string) (string, error) { cmd.Log.Info("Wait for DevPod Pro to become available...") // check for local port containerDetails, err := cmd.inspectContainer(ctx, containerID) if err != nil { return "", err } else if len(containerDetails.NetworkSettings.Ports) > 0 && len(containerDetails.NetworkSettings.Ports["10443/tcp"]) > 0 { return "localhost:" + containerDetails.NetworkSettings.Ports["10443/tcp"][0].HostPort, nil } // check if no tunnel if cmd.NoTunnel { return "", fmt.Errorf("%w: %s", ErrLoftNotReachable, "cannot connect to DevPod Pro as it has no exposed port and --no-tunnel is enabled") } // wait for router url := "" waitErr := wait.PollUntilContextTimeout(ctx, time.Second, time.Minute*10, true, func(ctx context.Context) (bool, error) { url, err = cmd.findLoftRouter(ctx, containerID) if err != nil { return false, nil } return true, nil }) if waitErr != nil { return "", fmt.Errorf("error waiting for loft router domain: %w", err) } return url, nil } func (cmd *StartCmd) findLoftRouter(ctx context.Context, id string) (string, error) { out, err := cmd.buildDockerCmd(ctx, "exec", id, "cat", "/var/lib/loft/loft-domain.txt").Output() if err != nil { return "", WrapCommandError(out, err) } return strings.TrimSpace(string(out)), nil } func (cmd *StartCmd) prepareDocker() error { // test for helm and kubectl _, err := exec.LookPath("docker") if err != nil { return fmt.Errorf("seems like docker is not installed. Docker is required for the installation of loft. Please visit https://docs.docker.com/engine/install/ for install instructions") } output, err := exec.Command("docker", "ps").CombinedOutput() if err != nil { return fmt.Errorf("seems like there are issues with your docker cli: \n\n%s", output) } return nil } func (cmd *StartCmd) uninstallDocker(ctx context.Context, id string) error { cmd.Log.Infof("Uninstalling...") // stop container out, err := cmd.buildDockerCmd(ctx, "stop", id).Output() if err != nil { return fmt.Errorf("stop container: %w", WrapCommandError(out, err)) } // remove container out, err = cmd.buildDockerCmd(ctx, "rm", id).Output() if err != nil { return fmt.Errorf("remove container: %w", WrapCommandError(out, err)) } return nil } func (cmd *StartCmd) runInDocker(ctx context.Context, name string) (string, error) { args := []string{"run", "-d", "--name", name} if cmd.NoTunnel { args = append(args, "--env", "DISABLE_LOFT_ROUTER=true") } if cmd.Password != "" { args = append(args, "--env", "ADMIN_PASSWORD_HASH="+hash.String(cmd.Password)) } // run as root otherwise we get permission errors args = append(args, "-u", "root") // mount the loft lib args = append(args, "-v", "loft-data:/var/lib/loft") // set port if cmd.LocalPort != "" { args = append(args, "-p", cmd.LocalPort+":10443") } // set extra args args = append(args, cmd.DockerArgs...) // set image if cmd.DockerImage != "" { args = append(args, cmd.DockerImage) } else if cmd.Version != "" { args = append(args, "ghcr.io/loft-sh/devpod-pro:"+strings.TrimPrefix(cmd.Version, "v")) } else { args = append(args, "ghcr.io/loft-sh/devpod-pro:latest") } cmd.Log.Infof("Start DevPod Pro via 'docker %s'", strings.Join(args, " ")) runCmd := cmd.buildDockerCmd(ctx, args...) runCmd.Stdout = os.Stdout runCmd.Stderr = os.Stderr err := runCmd.Run() if err != nil { return "", err } return cmd.findLoftContainer(ctx, name, false) } func (cmd *StartCmd) logsContainer(ctx context.Context, id string) (string, error) { args := []string{"logs", id} out, err := cmd.buildDockerCmd(ctx, args...).CombinedOutput() if err != nil { return "", fmt.Errorf("logs container: %w", WrapCommandError(out, err)) } return string(out), nil } func (cmd *StartCmd) inspectContainer(ctx context.Context, id string) (*ContainerDetails, error) { args := []string{"inspect", "--type", "container", id} out, err := cmd.buildDockerCmd(ctx, args...).Output() if err != nil { return nil, fmt.Errorf("inspect container: %w", WrapCommandError(out, err)) } containerDetails := []*ContainerDetails{} err = json.Unmarshal(out, &containerDetails) if err != nil { return nil, fmt.Errorf("parse inspect output: %w", err) } else if len(containerDetails) == 0 { return nil, fmt.Errorf("coudln't find container %s", id) } return containerDetails[0], nil } func (cmd *StartCmd) removeContainer(ctx context.Context, id string) error { args := []string{"rm", id} out, err := cmd.buildDockerCmd(ctx, args...).Output() if err != nil { return fmt.Errorf("remove container: %w", WrapCommandError(out, err)) } return nil } func (cmd *StartCmd) findLoftContainer(ctx context.Context, name string, onlyRunning bool) (string, error) { args := []string{"ps", "-q", "-a", "-f", "name=^" + name + "$"} out, err := cmd.buildDockerCmd(ctx, args...).Output() if err != nil { // fallback to manual search return "", fmt.Errorf("error finding container: %w", WrapCommandError(out, err)) } arr := []string{} scan := scanner.NewScanner(bytes.NewReader(out)) for scan.Scan() { arr = append(arr, strings.TrimSpace(scan.Text())) } if len(arr) == 0 { return "", nil } // remove the failed / exited containers runningContainerID := "" for _, containerID := range arr { containerState, err := cmd.inspectContainer(ctx, containerID) if err != nil { return "", err } else if onlyRunning && strings.ToLower(containerState.State.Status) != "running" { err = cmd.removeContainer(ctx, containerID) if err != nil { return "", err } } else { runningContainerID = containerID } } return runningContainerID, nil } func (cmd *StartCmd) buildDockerCmd(ctx context.Context, args ...string) *exec.Cmd { return exec.CommandContext(ctx, "docker", args...) } func (cmd *StartCmd) prepareInstall(ctx context.Context) error { // delete admin user & secret return uninstall(ctx, cmd.KubeClient, cmd.RestConfig, cmd.Context, cmd.Namespace, log.Discard) } func (cmd *StartCmd) prepare() error { loader, err := client.NewClientFromPath(cmd.Config) if err != nil { return err } loftConfig := loader.Config() // first load the kube config kubeClientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{}) // load the raw config kubeConfig, err := kubeClientConfig.RawConfig() if err != nil { return fmt.Errorf("there is an error loading your current kube config (%w), please make sure you have access to a kubernetes cluster and the command `kubectl get namespaces` is working", err) } // we switch the context to the install config contextToLoad := kubeConfig.CurrentContext if cmd.Context != "" { contextToLoad = cmd.Context } else if loftConfig.LastInstallContext != "" && loftConfig.LastInstallContext != contextToLoad { contextToLoad, err = cmd.Log.Question(&survey.QuestionOptions{ Question: "Seems like you try to use 'devpod pro start' with a different kubernetes context than before. Please choose which kubernetes context you want to use", DefaultValue: contextToLoad, Options: []string{contextToLoad, loftConfig.LastInstallContext}, }) if err != nil { return err } } cmd.Context = contextToLoad loftConfig.LastInstallContext = contextToLoad _ = loader.Save() // kube client config kubeClientConfig = clientcmd.NewNonInteractiveClientConfig(kubeConfig, contextToLoad, &clientcmd.ConfigOverrides{}, clientcmd.NewDefaultClientConfigLoadingRules()) // test for helm and kubectl _, err = exec.LookPath("helm") if err != nil { return fmt.Errorf("seems like helm is not installed. Helm is required for the installation of loft. Please visit https://helm.sh/docs/intro/install/ for install instructions") } output, err := exec.Command("helm", "version").CombinedOutput() if err != nil { return fmt.Errorf("seems like there are issues with your helm client: \n\n%s", output) } _, err = exec.LookPath("kubectl") if err != nil { return fmt.Errorf("seems like kubectl is not installed. Kubectl is required for the installation of loft. Please visit https://kubernetes.io/docs/tasks/tools/install-kubectl/ for install instructions") } output, err = exec.Command("kubectl", "version", "--context", contextToLoad).CombinedOutput() if err != nil { return fmt.Errorf("seems like kubectl cannot connect to your Kubernetes cluster: \n\n%s", output) } cmd.RestConfig, err = kubeClientConfig.ClientConfig() if err != nil { return fmt.Errorf("there is an error loading your current kube config (%w), please make sure you have access to a kubernetes cluster and the command `kubectl get namespaces` is working", err) } cmd.KubeClient, err = kubernetes.NewForConfig(cmd.RestConfig) if err != nil { return fmt.Errorf("there is an error loading your current kube config (%w), please make sure you have access to a kubernetes cluster and the command `kubectl get namespaces` is working", err) } // Check if cluster has RBAC correctly configured _, err = cmd.KubeClient.RbacV1().ClusterRoles().Get(context.Background(), "cluster-admin", metav1.GetOptions{}) if err != nil { return fmt.Errorf("error retrieving cluster role 'cluster-admin': %w. Please make sure RBAC is correctly configured in your cluster", err) } return nil } func (cmd *StartCmd) handleAlreadyExistingInstallation(ctx context.Context) error { enableIngress := false // Only ask if ingress should be enabled if --upgrade flag is not provided if !cmd.Upgrade && term.IsTerminal(os.Stdin) { cmd.Log.Info("Existing instance found.") // Check if Loft is installed in a local cluster isLocal := isInstalledLocally(ctx, cmd.KubeClient, cmd.Namespace) // Skip question if --host flag is provided if cmd.Host != "" { enableIngress = true } if enableIngress { if isLocal { // Confirm with user if this is a local cluster const ( YesOption = "Yes" NoOption = "No, my cluster is running not locally (GKE, EKS, Bare Metal, etc.)" ) answer, err := cmd.Log.Question(&survey.QuestionOptions{ Question: "Seems like your cluster is running locally (docker desktop, minikube, kind etc.). Is that correct?", DefaultValue: YesOption, Options: []string{ YesOption, NoOption, }, }) if err != nil { return err } isLocal = answer == YesOption } if isLocal { // Confirm with user if ingress should be installed in local cluster var ( YesOption = "Yes, enable the ingress anyway" NoOption = "No" ) answer, err := cmd.Log.Question(&survey.QuestionOptions{ Question: "Enabling ingress is usually only useful for remote clusters. Do you still want to deploy the ingress to your local cluster?", DefaultValue: NoOption, Options: []string{ NoOption, YesOption, }, }) if err != nil { return err } enableIngress = answer == YesOption } } // Check if we need to enable ingress if enableIngress { // Ask for hostname if --host flag is not provided if cmd.Host == "" { host, err := enterHostNameQuestion(cmd.Log) if err != nil { return err } cmd.Host = host } else { cmd.Log.Info("Will enable an ingress with hostname: " + cmd.Host) } if term.IsTerminal(os.Stdin) { err := ensureIngressController(ctx, cmd.KubeClient, cmd.Context, cmd.Log) if err != nil { return errors.Wrap(err, "install ingress controller") } } } } // Only upgrade if --upgrade flag is present or user decided to enable ingress if cmd.Upgrade || enableIngress { err := cmd.upgrade() if err != nil { return err } } return cmd.success(ctx) } func (cmd *StartCmd) waitForDeployment(ctx context.Context) (*corev1.Pod, error) { // wait for loft pod to start cmd.Log.Info("Waiting for DevPod Pro pod to be running...") loftPod, err := platform.WaitForPodReady(ctx, cmd.KubeClient, cmd.Namespace, cmd.Log) cmd.Log.Donef("Release Pod successfully started") if err != nil { return nil, err } // ensure user admin secret is there isNewPassword, err := ensureAdminPassword(ctx, cmd.KubeClient, cmd.RestConfig, cmd.Password, cmd.Log) if err != nil { return nil, err } // If password is different than expected if isNewPassword { cmd.Password = "" } return loftPod, nil } func (cmd *StartCmd) pingLoftRouter(ctx context.Context, loftPod *corev1.Pod) (string, error) { loftRouterSecret, err := cmd.KubeClient.CoreV1().Secrets(loftPod.Namespace).Get(ctx, LoftRouterDomainSecret, metav1.GetOptions{}) if err != nil { if kerrors.IsNotFound(err) { return "", nil } return "", fmt.Errorf("find loft router domain secret: %w", err) } else if loftRouterSecret.Data == nil || len(loftRouterSecret.Data["domain"]) == 0 { return "", nil } // get the domain from secret loftRouterDomain := string(loftRouterSecret.Data["domain"]) // wait until loft is reachable at the given url httpClient := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, }, } cmd.Log.Infof("Waiting until DevPod Pro is reachable at https://%s", loftRouterDomain) err = wait.PollUntilContextTimeout(ctx, time.Second*3, time.Minute*5, true, func(ctx context.Context) (bool, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://"+loftRouterDomain+"/version", nil) if err != nil { return false, nil } resp, err := httpClient.Do(req) if err != nil { return false, nil } return resp.StatusCode == http.StatusOK, nil }) if err != nil { return "", err } return loftRouterDomain, nil } func (cmd *StartCmd) successLoftRouter(url string) error { if !cmd.NoLogin { err := cmd.login(url) if err != nil { return err } } url = "https://" + url password := cmd.Password if password == "" { password = passwordChangedHint } cmd.Log.WriteString(logrus.InfoLevel, fmt.Sprintf(` ########################## LOGIN ############################ Username: `+ansi.Color("admin", "green+b")+` Password: `+ansi.Color(password, "green+b")+` # Change via UI or via: `+ansi.Color("devpod pro reset password", "green+b")+` Login via UI: %s Login via CLI: %s ################################################################# DevPod Pro was successfully installed and can now be reached at: %s Thanks for using DevPod Pro! `, ansi.Color(url, "green+b"), ansi.Color("devpod pro login"+" "+url, "green+b"), url, )) return nil } func (cmd *StartCmd) login(url string) error { if !strings.HasPrefix(url, "https://") { url = "https://" + url } // check if we are already logged in if cmd.isLoggedIn(url) { // still open the UI err := open.Run(url) if err != nil { return fmt.Errorf("couldn't open the login page in a browser: %w", err) } return nil } // log into the CLI err := cmd.loginViaCLI(url) if err != nil { return err } // log into the UI err = cmd.loginUI(url) if err != nil { return err } return nil } func (cmd *StartCmd) loginViaCLI(url string) error { loginPath := "%s/auth/password/login" loginRequest := auth.PasswordLoginRequest{ Username: defaultUser, Password: cmd.Password, } loginRequestBytes, err := json.Marshal(loginRequest) if err != nil { return err } loginRequestBuf := bytes.NewBuffer(loginRequestBytes) tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } httpClient := &http.Client{Transport: tr} resp, err := httpClient.Post(fmt.Sprintf(loginPath, url), "application/json", loginRequestBuf) if err != nil { return err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return err } accessKey := &auth.AccessKey{} err = json.Unmarshal(body, accessKey) if err != nil { return err } // log into loft loader, err := client.NewClientFromPath(cmd.Config) if err != nil { return err } url = strings.TrimSuffix(url, "/") err = loader.LoginWithAccessKey(url, accessKey.AccessKey, true, false) if err != nil { return err } cmd.Log.WriteString(logrus.InfoLevel, "\n") cmd.Log.Donef("Successfully logged in via CLI into %s", ansi.Color(url, "white+b")) return nil } func (cmd *StartCmd) loginUI(url string) error { queryString := fmt.Sprintf("username=%s&password=%s", defaultUser, netUrl.QueryEscape(cmd.Password)) loginURL := fmt.Sprintf("%s/login#%s", url, queryString) err := open.Run(loginURL) if err != nil { return fmt.Errorf("couldn't open the login page in a browser: %w", err) } cmd.Log.Infof("If the browser does not open automatically, please navigate to %s", loginURL) return nil } func (cmd *StartCmd) isLoggedIn(url string) bool { url = strings.TrimPrefix(url, "https://") c, err := client.NewClientFromPath(cmd.Config) return err == nil && strings.TrimPrefix(strings.TrimSuffix(c.Config().Host, "/"), "https://") == strings.TrimSuffix(url, "/") } func uninstall(ctx context.Context, kubeClient kubernetes.Interface, restConfig *rest.Config, kubeContext, namespace string, log log.Logger) error { releaseName := "devpod-pro" deploy, err := kubeClient.AppsV1().Deployments(namespace).Get(ctx, defaultDeploymentName, metav1.GetOptions{}) if err != nil && !kerrors.IsNotFound(err) { return err } else if deploy != nil && deploy.Labels != nil && deploy.Labels["release"] != "" { releaseName = deploy.Labels["release"] } args := []string{ "uninstall", releaseName, "--kube-context", kubeContext, "--namespace", namespace, } log.Infof("Executing command: helm %s", strings.Join(args, " ")) output, err := exec.Command("helm", args...).CombinedOutput() if err != nil { log.Errorf("error during helm command: %s (%v)", string(output), err) } // we also cleanup the validating webhook configuration and apiservice apiRegistrationClient, err := clientset.NewForConfig(restConfig) if err != nil { return err } err = apiRegistrationClient.ApiregistrationV1().APIServices().Delete(ctx, "v1.management.loft.sh", metav1.DeleteOptions{}) if err != nil && !kerrors.IsNotFound(err) { return err } err = deleteUser(ctx, restConfig, "admin") if err != nil { return err } err = kubeClient.CoreV1().Secrets(namespace).Delete(context.Background(), "loft-user-secret-admin", metav1.DeleteOptions{}) if err != nil && !kerrors.IsNotFound(err) { return err } err = kubeClient.CoreV1().Secrets(namespace).Delete(context.Background(), LoftRouterDomainSecret, metav1.DeleteOptions{}) if err != nil && !kerrors.IsNotFound(err) { return err } // we also cleanup the validating webhook configuration and apiservice err = kubeClient.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(ctx, "loft-agent", metav1.DeleteOptions{}) if err != nil && !kerrors.IsNotFound(err) { return err } err = apiRegistrationClient.ApiregistrationV1().APIServices().Delete(ctx, "v1alpha1.tenancy.kiosk.sh", metav1.DeleteOptions{}) if err != nil && !kerrors.IsNotFound(err) { return err } err = apiRegistrationClient.ApiregistrationV1().APIServices().Delete(ctx, "v1.cluster.loft.sh", metav1.DeleteOptions{}) if err != nil && !kerrors.IsNotFound(err) { return err } err = kubeClient.CoreV1().ConfigMaps(namespace).Delete(ctx, "loft-agent-controller", metav1.DeleteOptions{}) if err != nil && !kerrors.IsNotFound(err) { return err } err = kubeClient.CoreV1().ConfigMaps(namespace).Delete(ctx, "loft-applied-defaults", metav1.DeleteOptions{}) if err != nil && !kerrors.IsNotFound(err) { return err } log.WriteString(logrus.InfoLevel, "\n") log.Done("Successfully uninstalled DevPod Pro") log.WriteString(logrus.InfoLevel, "\n") return nil } func isAlreadyInstalled(ctx context.Context, kubeClient kubernetes.Interface, namespace string) (bool, error) { _, err := kubeClient.AppsV1().Deployments(namespace).Get(ctx, defaultDeploymentName, metav1.GetOptions{}) if err != nil { if kerrors.IsNotFound(err) { return false, nil } return false, fmt.Errorf("error accessing kubernetes cluster: %w", err) } return true, nil } func getDefaultPassword(ctx context.Context, kubeClient kubernetes.Interface, namespace string) (string, error) { loftNamespace, err := kubeClient.CoreV1().Namespaces().Get(ctx, namespace, metav1.GetOptions{}) if err != nil { if kerrors.IsNotFound(err) { loftNamespace, err := kubeClient.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: namespace, }, }, metav1.CreateOptions{}) if err != nil { return "", err } return string(loftNamespace.UID), nil } return "", err } return string(loftNamespace.UID), nil } func isInstalledLocally(ctx context.Context, kubeClient kubernetes.Interface, namespace string) bool { _, err := kubeClient.NetworkingV1().Ingresses(namespace).Get(ctx, "loft-ingress", metav1.GetOptions{}) if err != nil && !kerrors.IsNotFound(err) { _, err = kubeClient.NetworkingV1beta1().Ingresses(namespace).Get(ctx, "loft-ingress", metav1.GetOptions{}) return kerrors.IsNotFound(err) } return kerrors.IsNotFound(err) } func enterHostNameQuestion(log log.Logger) (string, error) { return log.Question(&survey.QuestionOptions{ Question: fmt.Sprintf("Enter a hostname for your %s instance (e.g. loft.my-domain.tld): \n ", "DevPod Pro"), ValidationFunc: func(answer string) error { u, err := netUrl.Parse("https://" + answer) if err != nil || u.Path != "" || u.Port() != "" || len(strings.Split(answer, ".")) < 2 { return fmt.Errorf("please enter a valid hostname without protocol (https://), without path and without port, e.g. loft.my-domain.tld") } return nil }, }) } func ensureIngressController(ctx context.Context, kubeClient kubernetes.Interface, kubeContext string, log log.Logger) error { // first create an ingress controller const ( YesOption = "Yes" NoOption = "No, I already have an ingress controller installed." ) answer, err := log.Question(&survey.QuestionOptions{ Question: "Ingress controller required. Should the nginx-ingress controller be installed?", DefaultValue: YesOption, Options: []string{ YesOption, NoOption, }, }) if err != nil { return err } if answer == YesOption { args := []string{ "install", "ingress-nginx", "ingress-nginx", "--repository-config=''", "--repo", "https://kubernetes.github.io/ingress-nginx", "--kube-context", kubeContext, "--namespace", "ingress-nginx", "--create-namespace", "--set-string", "controller.config.hsts=false", "--wait", } log.WriteString(logrus.InfoLevel, "\n") log.Infof("Executing command: helm %s\n", strings.Join(args, " ")) log.Info("Waiting for ingress controller deployment, this can take several minutes...") helmCmd := exec.Command("helm", args...) output, err := helmCmd.CombinedOutput() if err != nil { return fmt.Errorf("error during helm command: %s (%w)", string(output), err) } list, err := kubeClient.CoreV1().Secrets("ingress-nginx").List(ctx, metav1.ListOptions{ LabelSelector: "name=ingress-nginx,owner=helm,status=deployed", }) if err != nil { return err } if len(list.Items) == 1 { secret := list.Items[0] originalSecret := secret.DeepCopy() secret.Labels["loft.sh/app"] = "true" if secret.Annotations == nil { secret.Annotations = map[string]string{} } secret.Annotations["loft.sh/url"] = "https://kubernetes.github.io/ingress-nginx" originalJSON, err := json.Marshal(originalSecret) if err != nil { return err } modifiedJSON, err := json.Marshal(secret) if err != nil { return err } data, err := jsonpatch.CreateMergePatch(originalJSON, modifiedJSON) if err != nil { return err } _, err = kubeClient.CoreV1().Secrets(secret.Namespace).Patch(ctx, secret.Name, types.MergePatchType, data, metav1.PatchOptions{}) if err != nil { return err } } log.Done("Successfully installed ingress-nginx to your kubernetes cluster!") } return nil } func deleteUser(ctx context.Context, restConfig *rest.Config, name string) error { loftClient, err := loftclientset.NewForConfig(restConfig) if err != nil { return err } user, err := loftClient.StorageV1().Users().Get(ctx, name, metav1.GetOptions{}) if err != nil { return nil } else if len(user.Finalizers) > 0 { user.Finalizers = nil _, err = loftClient.StorageV1().Users().Update(ctx, user, metav1.UpdateOptions{}) if err != nil { if kerrors.IsConflict(err) { return deleteUser(ctx, restConfig, name) } return err } } err = loftClient.StorageV1().Users().Delete(ctx, name, metav1.DeleteOptions{}) if err != nil && !kerrors.IsNotFound(err) { return err } return nil } func ensureAdminPassword(ctx context.Context, kubeClient kubernetes.Interface, restConfig *rest.Config, password string, log log.Logger) (bool, error) { loftClient, err := loftclientset.NewForConfig(restConfig) if err != nil { return false, err } admin, err := loftClient.StorageV1().Users().Get(ctx, "admin", metav1.GetOptions{}) if err != nil && !kerrors.IsNotFound(err) { return false, err } else if admin == nil { admin, err = loftClient.StorageV1().Users().Create(ctx, &storagev1.User{ ObjectMeta: metav1.ObjectMeta{ Name: "admin", }, Spec: storagev1.UserSpec{ Username: "admin", Email: "test@domain.tld", Subject: "admin", Groups: []string{"system:masters"}, PasswordRef: &storagev1.SecretRef{ SecretName: "loft-user-secret-admin", SecretNamespace: "loft", Key: "password", }, }, }, metav1.CreateOptions{}) if err != nil { return false, err } } else if admin.Spec.PasswordRef == nil || admin.Spec.PasswordRef.SecretName == "" || admin.Spec.PasswordRef.SecretNamespace == "" { return false, nil } key := admin.Spec.PasswordRef.Key if key == "" { key = "password" } passwordHash := fmt.Sprintf("%x", sha256.Sum256([]byte(password))) secret, err := kubeClient.CoreV1().Secrets(admin.Spec.PasswordRef.SecretNamespace).Get(ctx, admin.Spec.PasswordRef.SecretName, metav1.GetOptions{}) if err != nil && !kerrors.IsNotFound(err) { return false, err } else if err == nil { existingPasswordHash, keyExists := secret.Data[key] if keyExists { return (string(existingPasswordHash) != passwordHash), nil } secret.Data[key] = []byte(passwordHash) _, err = kubeClient.CoreV1().Secrets(secret.Namespace).Update(ctx, secret, metav1.UpdateOptions{}) if err != nil { return false, errors.Wrap(err, "update admin password secret") } return false, nil } // create the password secret if it was not found, this can happen if you delete the loft namespace without deleting the admin user secret = &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: admin.Spec.PasswordRef.SecretName, Namespace: admin.Spec.PasswordRef.SecretNamespace, }, Data: map[string][]byte{ key: []byte(passwordHash), }, } _, err = kubeClient.CoreV1().Secrets(secret.Namespace).Create(ctx, secret, metav1.CreateOptions{}) if err != nil { return false, errors.Wrap(err, "create admin password secret") } log.Info("Successfully recreated admin password secret") return false, nil } func getIngressHost(ctx context.Context, kubeClient kubernetes.Interface, namespace string) (string, error) { ingress, err := kubeClient.NetworkingV1().Ingresses(namespace).Get(ctx, "loft-ingress", metav1.GetOptions{}) if err != nil { ingress, err := kubeClient.NetworkingV1beta1().Ingresses(namespace).Get(ctx, "loft-ingress", metav1.GetOptions{}) if err != nil { return "", err } else { // find host for _, rule := range ingress.Spec.Rules { return rule.Host, nil } } } else { // find host for _, rule := range ingress.Spec.Rules { return rule.Host, nil } } return "", fmt.Errorf("couldn't find any host in loft ingress '%s/loft-ingress', please make sure you have not changed any deployed resources", namespace) } type version struct { Version string `json:"version"` } func isHostReachable(ctx context.Context, host string) (bool, error) { transport := http.DefaultTransport.(*http.Transport).Clone() // we disable http2 as Kubernetes has problems with this transport.ForceAttemptHTTP2 = false transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} // wait until loft is reachable at the given url client := &http.Client{Transport: transport} url := "https://" + host + "/version" req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return false, fmt.Errorf("error creating request with context: %w", err) } resp, err := client.Do(req) if err == nil && resp.StatusCode == http.StatusOK { out, err := io.ReadAll(resp.Body) if err != nil { return false, nil } v := &version{} err = json.Unmarshal(out, v) if err != nil { return false, fmt.Errorf("error decoding response from %s: %w. Try running '%s --reset'", url, err, "devpod pro start") } else if v.Version == "" { return false, fmt.Errorf("unexpected response from %s: %s. Try running '%s --reset'", url, string(out), "devpod pro start") } return true, nil } return false, nil } func upgradeRelease(chartName, chartRepo, kubeContext, namespace string, extraArgs []string, log log.Logger) error { // now we install loft args := []string{ "upgrade", defaultReleaseName, chartName, "--install", "--create-namespace", "--repository-config=''", "--kube-context", kubeContext, "--namespace", namespace, } if chartRepo != "" { args = append(args, "--repo", chartRepo) } args = append(args, extraArgs...) log.WriteString(logrus.InfoLevel, "\n") log.Infof("Executing command: helm %s\n", strings.Join(args, " ")) log.Info("Waiting for helm command, this can take up to several minutes...") helmCmd := exec.Command("helm", args...) if chartRepo != "" { helmWorkDir, err := getHelmWorkdir(chartName) if err != nil { return err } helmCmd.Dir = helmWorkDir } output, err := helmCmd.CombinedOutput() if err != nil { return fmt.Errorf("error during helm command: %s (%w)", string(output), err) } log.Donef("DevPod Pro has been deployed to your cluster!") return nil } func getReleaseManifests(chartName, chartRepo, kubeContext, namespace string, extraArgs []string, _ log.Logger) (string, error) { args := []string{ "template", defaultReleaseName, chartName, "--repository-config=''", "--kube-context", kubeContext, "--namespace", namespace, } if chartRepo != "" { args = append(args, "--repo", chartRepo) } args = append(args, extraArgs...) helmCmd := exec.Command("helm", args...) if chartRepo != "" { helmWorkDir, err := getHelmWorkdir(chartName) if err != nil { return "", err } helmCmd.Dir = helmWorkDir } output, err := helmCmd.CombinedOutput() if err != nil { return "", fmt.Errorf("error during helm command: %s (%w)", string(output), err) } return string(output), nil } func getHelmWorkdir(chartName string) (string, error) { // If chartName folder exists, check temp dir next if _, err := os.Stat(chartName); err == nil { tempDir := os.TempDir() // If tempDir/chartName folder exists, create temp folder if _, err := os.Stat(path.Join(tempDir, chartName)); err == nil { tempDir, err = os.MkdirTemp(tempDir, chartName) if err != nil { return "", errors.New("problematic directory `" + chartName + "` found: please execute command in a different folder") } } // Use tempDir return tempDir, nil } // Use current workdir return "", nil } var ( ErrMissingContainer = errors.New("missing container") ErrLoftNotReachable = errors.New("DevPod Pro is not reachable") ) type ContainerDetails struct { NetworkSettings ContainerNetworkSettings `json:"NetworkSettings,omitempty"` State ContainerDetailsState `json:"State,omitempty"` ID string `json:"ID,omitempty"` Created string `json:"Created,omitempty"` Config ContainerDetailsConfig `json:"Config,omitempty"` } type ContainerNetworkSettings struct { Ports map[string][]ContainerPort `json:"ports,omitempty"` } type ContainerPort struct { HostIP string `json:"HostIp,omitempty"` HostPort string `json:"HostPort,omitempty"` } type ContainerDetailsConfig struct { Labels map[string]string `json:"Labels,omitempty"` Image string `json:"Image,omitempty"` User string `json:"User,omitempty"` Env []string `json:"Env,omitempty"` } type ContainerDetailsState struct { Status string `json:"Status,omitempty"` StartedAt string `json:"StartedAt,omitempty"` } func WrapCommandError(stdout []byte, err error) error { if err == nil { return nil } return &Error{ stdout: stdout, err: err, } } type Error struct { err error stdout []byte } func (e *Error) Error() string { message := "" if len(e.stdout) > 0 { message += string(e.stdout) + "\n" } var exitError *exec.ExitError if errors.As(e.err, &exitError) && len(exitError.Stderr) > 0 { message += string(exitError.Stderr) + "\n" } return message + e.err.Error() } func getMachineUID(log log.Logger) string { id, err := machineid.ID() if err != nil { id = "error" if log != nil { log.Debugf("Error retrieving machine uid: %v", err) } } // get $HOME to distinguish two users on the same machine // will be hashed later together with the ID home, err := util.UserHomeDir() if err != nil { home = "error" if log != nil { log.Debugf("Error retrieving machine home: %v", err) } } mac := hmac.New(sha256.New, []byte(id)) mac.Write([]byte(home)) return fmt.Sprintf("%x", mac.Sum(nil)) } ================================================ FILE: cmd/pro/update_provider.go ================================================ package pro import ( "context" "fmt" "strings" "github.com/loft-sh/devpod/cmd/pro/flags" providercmd "github.com/loft-sh/devpod/cmd/provider" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" ) // UpdateProviderCmd holds the cmd flags type UpdateProviderCmd struct { *flags.GlobalFlags Log log.Logger Host string Instance string } // NewUpdateProviderCmd creates a new command func NewUpdateProviderCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &UpdateProviderCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "update-provider [new-version]", Short: "Update platform provider", Hidden: true, RunE: func(cobraCmd *cobra.Command, args []string) error { return cmd.Run(cobraCmd.Context(), args) }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") return c } func (cmd *UpdateProviderCmd) Run(ctx context.Context, args []string) error { if len(args) != 1 { return fmt.Errorf("new version is missing") } newVersion := args[0] devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } provider, err := workspace.ProviderFromHost(ctx, devPodConfig, cmd.Host, cmd.Log) if err != nil { return fmt.Errorf("load provider: %w", err) } if provider.Source.Internal { return nil } providerSource, err := workspace.ResolveProviderSource(devPodConfig, provider.Name, cmd.Log) if err != nil { return fmt.Errorf("resolve provider source %s: %w", provider.Name, err) } splitted := strings.Split(providerSource, "@") if len(splitted) == 0 { return fmt.Errorf("no provider source found %s", providerSource) } providerSource = splitted[0] + "@" + newVersion _, err = workspace.UpdateProvider(devPodConfig, provider.Name, providerSource, cmd.Log) if err != nil { return fmt.Errorf("update provider %s: %w", provider.Name, err) } err = providercmd.ConfigureProvider(ctx, provider, devPodConfig.DefaultContext, []string{}, true, true, true, nil, log.Discard) if err != nil { return fmt.Errorf("configure provider, please retry with 'devpod provider use %s --reconfigure': %w", provider.Name, err) } return nil } ================================================ FILE: cmd/pro/update_workspace.go ================================================ package pro import ( "bytes" "context" "fmt" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // UpdateWorkspaceCmd holds the cmd flags type UpdateWorkspaceCmd struct { *flags.GlobalFlags Log log.Logger Host string Instance string } // NewUpdateWorkspaceCmd creates a new command func NewUpdateWorkspaceCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &UpdateWorkspaceCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "update-workspace", Short: "Update workspace instance", Hidden: true, RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") c.Flags().StringVar(&cmd.Instance, "instance", "", "The workspace instance to update") _ = c.MarkFlagRequired("instance") return c } func (cmd *UpdateWorkspaceCmd) Run(ctx context.Context, devPodConfig *config.Config, provider *provider.ProviderConfig) error { opts := devPodConfig.ProviderOptions(provider.Name) opts[platform.WorkspaceInstanceEnv] = config.OptionValue{Value: cmd.Instance} var buf bytes.Buffer // ignore --debug because we tunnel json through stdio cmd.Log.SetLevel(logrus.InfoLevel) err := clientimplementation.RunCommandWithBinaries( ctx, "updateWorkspace", provider.Exec.Proxy.Update.Workspace, devPodConfig.DefaultContext, nil, nil, opts, provider, nil, nil, &buf, cmd.Log.ErrorStreamOnly().Writer(logrus.ErrorLevel, true), cmd.Log) if err != nil { return fmt.Errorf("update workspace with provider \"%s\": %w", provider.Name, err) } fmt.Println(buf.String()) return nil } ================================================ FILE: cmd/pro/version.go ================================================ package pro import ( "bytes" "context" "fmt" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // VersionCmd holds the cmd flags type VersionCmd struct { *flags.GlobalFlags Log log.Logger Host string } // NewVersionCmd creates a new command func NewVersionCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &VersionCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "version", Short: "Get version", Hidden: true, RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") return c } func (cmd *VersionCmd) Run(ctx context.Context, devPodConfig *config.Config, providerConfig *provider.ProviderConfig) error { opts := devPodConfig.ProviderOptions(providerConfig.Name) opts[provider.PROVIDER_ID] = config.OptionValue{Value: providerConfig.Name} opts[provider.PROVIDER_CONTEXT] = config.OptionValue{Value: cmd.Context} var buf bytes.Buffer // ignore --debug because we tunnel json through stdio cmd.Log.SetLevel(logrus.InfoLevel) err := clientimplementation.RunCommandWithBinaries( ctx, "getVersion", providerConfig.Exec.Proxy.Get.Version, devPodConfig.DefaultContext, nil, nil, opts, providerConfig, nil, nil, &buf, nil, cmd.Log) if err != nil { return fmt.Errorf("get version: %w", err) } fmt.Print(buf.String()) return nil } ================================================ FILE: cmd/pro/wakeup.go ================================================ package pro import ( "context" "fmt" "strconv" "time" clusterv1 "github.com/loft-sh/agentapi/v4/pkg/apis/loft/cluster/v1" storagev1 "github.com/loft-sh/api/v4/pkg/apis/storage/v1" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/platform/project" "github.com/loft-sh/log" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" ) // WakeupCmd holds the cmd flags type WakeupCmd struct { *flags.GlobalFlags Log log.Logger Project string Host string } // NewWakeupCmd creates a new command func NewWakeupCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &WakeupCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "wakeup", Short: "Wake a workspace up", RunE: func(cobraCmd *cobra.Command, args []string) error { log.Default.SetFormat(log.TextFormat) return cmd.Run(cobraCmd.Context(), args) }, } c.Flags().StringVar(&cmd.Project, "project", "", "The project to use") _ = c.MarkFlagRequired("project") c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") return c } func (cmd *WakeupCmd) Run(ctx context.Context, args []string) error { if len(args) == 0 { return fmt.Errorf("please provide a workspace name") } targetWorkspace := args[0] devPodConfig, err := config.LoadConfig(cmd.Context, "") if err != nil { return err } baseClient, err := platform.InitClientFromHost(ctx, devPodConfig, cmd.Host, cmd.Log) if err != nil { return err } workspaceInstance, err := platform.FindInstanceByName(ctx, baseClient, targetWorkspace, cmd.Project) if err != nil { return err } if workspaceInstance.Status.Phase != storagev1.InstanceSleeping { cmd.Log.Infof("Workspace %s is not sleeping", targetWorkspace, workspaceInstance.Name) return nil } managementClient, err := baseClient.Management() if err != nil { return err } // create a deep copy of the workspace instance oldWorkspaceInstance := workspaceInstance.DeepCopy() oldWorkspaceInstance.Status = workspaceInstance.Status oldWorkspaceInstance.ObjectMeta = workspaceInstance.ObjectMeta oldWorkspaceInstance.TypeMeta = workspaceInstance.TypeMeta // create a patch from the old workspace instance patch := ctrlclient.MergeFrom(oldWorkspaceInstance) if workspaceInstance.Annotations == nil { workspaceInstance.Annotations = map[string]string{} } delete(workspaceInstance.Annotations, clusterv1.SleepModeForceAnnotation) delete(workspaceInstance.Annotations, clusterv1.SleepModeForceDurationAnnotation) workspaceInstance.Annotations[clusterv1.SleepModeLastActivityAnnotation] = strconv.FormatInt(time.Now().Unix(), 10) patchData, err := patch.Data(workspaceInstance) if err != nil { return err } _, err = managementClient.Loft().ManagementV1().DevPodWorkspaceInstances(project.ProjectNamespace(cmd.Project)).Patch(ctx, workspaceInstance.Name, patch.Type(), patchData, metav1.PatchOptions{}) if err != nil { return err } // wait for sleeping cmd.Log.Info("Wait until workspace wakes up...") err = wait.PollUntilContextTimeout(ctx, time.Second, platform.Timeout(), false, func(ctx context.Context) (done bool, err error) { workspaceInstance, err := managementClient.Loft().ManagementV1().DevPodWorkspaceInstances(project.ProjectNamespace(cmd.Project)).Get(ctx, workspaceInstance.Name, metav1.GetOptions{}) if err != nil { return false, err } return workspaceInstance.Status.Phase == storagev1.InstanceReady, nil }) if err != nil { return fmt.Errorf("error waiting for workspace to wake up: %w", err) } cmd.Log.Donef("Successfully woke up workspace %s", workspaceInstance.Name) return nil } ================================================ FILE: cmd/pro/watch_workspaces.go ================================================ package pro import ( "context" "fmt" "os" "os/signal" "syscall" "github.com/loft-sh/devpod/cmd/pro/flags" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // WatchWorkspacesCmd holds the cmd flags type WatchWorkspacesCmd struct { *flags.GlobalFlags Log log.Logger Host string Project string FilterByOwner bool } // NewWatchWorkspacesCmd creates a new command func NewWatchWorkspacesCmd(globalFlags *flags.GlobalFlags) *cobra.Command { cmd := &WatchWorkspacesCmd{ GlobalFlags: globalFlags, Log: log.GetInstance(), } c := &cobra.Command{ Use: "watch-workspaces", Short: "Watch workspaces", Hidden: true, RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, provider, err := findProProvider(cobraCmd.Context(), cmd.Context, cmd.Provider, cmd.Host, cmd.Log) if err != nil { return err } return cmd.Run(cobraCmd.Context(), devPodConfig, provider) }, } c.Flags().StringVar(&cmd.Host, "host", "", "The pro instance to use") _ = c.MarkFlagRequired("host") c.Flags().StringVar(&cmd.Project, "project", "", "The project to use") _ = c.MarkFlagRequired("project") c.Flags().BoolVar(&cmd.FilterByOwner, "filter-by-owner", true, "If true only shows workspaces of current owner") return c } func (cmd *WatchWorkspacesCmd) Run(ctx context.Context, devPodConfig *config.Config, providerConfig *provider.ProviderConfig) error { opts := devPodConfig.ProviderOptions(providerConfig.Name) cancelCtx, cancel := context.WithCancel(ctx) defer cancel() if cmd.FilterByOwner { opts[provider.LOFT_FILTER_BY_OWNER] = config.OptionValue{Value: "true"} } opts[provider.LOFT_PROJECT] = config.OptionValue{Value: cmd.Project} sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT) go func() { <-sigChan cancel() }() // ignore --debug because we tunnel json through stdio cmd.Log.SetLevel(logrus.InfoLevel) err := clientimplementation.RunCommandWithBinaries( cancelCtx, "watchWorkspaces", providerConfig.Exec.Proxy.Watch.Workspaces, devPodConfig.DefaultContext, nil, nil, opts, providerConfig, nil, nil, os.Stdout, log.Default.ErrorStreamOnly().Writer(logrus.ErrorLevel, false), cmd.Log) if err != nil { return fmt.Errorf("watch workspaces with provider \"%s\": %w", providerConfig.Name, err) } return nil } ================================================ FILE: cmd/profile.go ================================================ //go:build profile package cmd import ( "fmt" "net" "net/http" "net/http/pprof" "os" ) func init() { go func() { myMux := http.NewServeMux() myMux.HandleFunc("/debug/pprof/", pprof.Index) myMux.HandleFunc("/debug/pprof/{action}", pprof.Index) myMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) listener, err := net.Listen("tcp", ":0") if err != nil { return } f, err := os.OpenFile("/tmp/pprof_ports", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err == nil { f.Write([]byte(fmt.Sprintf("%d=%d\n", os.Getpid(), listener.Addr().(*net.TCPAddr).Port))) f.Close() } http.Serve(listener, myMux) }() } ================================================ FILE: cmd/provider/add.go ================================================ package provider import ( "context" "fmt" "strings" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/types" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" ) // AddCmd holds the cmd flags type AddCmd struct { *flags.GlobalFlags Use bool SingleMachine bool Options []string Name string FromExisting string } // NewAddCmd creates a new command func NewAddCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &AddCmd{ GlobalFlags: flags, } addCmd := &cobra.Command{ Use: "add [URL or path]", Short: "Adds a new provider to DevPod", PreRunE: func(cobraCommand *cobra.Command, args []string) error { if cmd.FromExisting != "" { return cobraCommand.MarkFlagRequired("name") } return nil }, RunE: func(_ *cobra.Command, args []string) error { ctx := context.Background() devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } return cmd.Run(ctx, devPodConfig, args) }, } addCmd.Flags().BoolVar(&cmd.SingleMachine, "single-machine", false, "If enabled will use a single machine for all workspaces") addCmd.Flags().StringVar(&cmd.Name, "name", "", "The name to use for this provider. If empty will use the name within the loaded config") addCmd.Flags().StringVar(&cmd.FromExisting, "from-existing", "", "The name of an existing provider to use as a template. Needs to be used in conjunction with the --name flag") addCmd.Flags().BoolVar(&cmd.Use, "use", true, "If enabled will automatically activate the provider") addCmd.Flags().StringArrayVarP(&cmd.Options, "option", "o", []string{}, "Provider option in the form KEY=VALUE") return addCmd } func (cmd *AddCmd) Run(ctx context.Context, devPodConfig *config.Config, args []string) error { if len(args) != 1 && cmd.FromExisting == "" { return fmt.Errorf("please specify either a local file, url or git repository. E.g. devpod provider add https://path/to/my/provider.yaml") } else if cmd.Name != "" && provider.ProviderNameRegEx.MatchString(cmd.Name) { return fmt.Errorf("provider name can only include smaller case letters, numbers or dashes") } else if cmd.Name != "" && len(cmd.Name) > 32 { return fmt.Errorf("provider name cannot be longer than 32 characters") } else if cmd.FromExisting != "" && devPodConfig.Current() != nil && devPodConfig.Current().Providers[cmd.FromExisting] == nil { return fmt.Errorf("provider %s does not exist", cmd.FromExisting) } var providerConfig *provider.ProviderConfig var options []string if cmd.FromExisting != "" { providerWithOptions, err := workspace.CloneProvider(devPodConfig, cmd.Name, cmd.FromExisting, log.Default) if err != nil { return err } providerConfig = providerWithOptions.Config options = mergeOptions(providerWithOptions.Config.Options, providerWithOptions.State.Options, cmd.Options) } else { c, err := workspace.AddProvider(devPodConfig, cmd.Name, args[0], log.Default) if err != nil { return err } providerConfig = c options = cmd.Options } log.Default.Donef("Successfully installed provider %s", providerConfig.Name) if cmd.Use { configureErr := ConfigureProvider(ctx, providerConfig, devPodConfig.DefaultContext, options, true, false, false, &cmd.SingleMachine, log.Default) if configureErr != nil { devPodConfig, err := config.LoadConfig(cmd.Context, "") if err != nil { return err } err = DeleteProvider(ctx, devPodConfig, providerConfig.Name, true, true, log.Default) if err != nil { return errors.Wrap(err, "delete provider") } return errors.Wrap(configureErr, "configure provider") } return nil } log.Default.Infof("To use the provider, please run the following command:") log.Default.Infof("devpod provider use %s", providerConfig.Name) return nil } // mergeOptions combines user options with existing options, user provided options take precedence func mergeOptions(desiredOptions map[string]*types.Option, stateOptions map[string]config.OptionValue, userOptions []string) []string { retOptions := []string{} for key := range desiredOptions { userOption, ok := getUserOption(userOptions, key) if ok { retOptions = append(retOptions, userOption) continue } stateOption, ok := stateOptions[key] if !ok { continue } retOptions = append(retOptions, fmt.Sprintf("%s=%s", key, stateOption.Value)) } return retOptions } func getUserOption(allOptions []string, optionKey string) (string, bool) { if len(allOptions) == 0 { return "", false } for _, option := range allOptions { splitted := strings.Split(option, "=") if len(splitted) == 1 { // ignore continue } if splitted[0] == optionKey { return option, true } } return "", false } ================================================ FILE: cmd/provider/delete.go ================================================ package provider import ( "context" "fmt" "os" "github.com/loft-sh/devpod/cmd/completion" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/platform" provider2 "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/workspace" logpkg "github.com/loft-sh/log" "github.com/spf13/cobra" ) // DeleteCmd holds the delete cmd flags type DeleteCmd struct { *flags.GlobalFlags IgnoreNotFound bool Force bool } // NewDeleteCmd creates a new command func NewDeleteCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &DeleteCmd{ GlobalFlags: flags, } deleteCmd := &cobra.Command{ Use: "delete [name]", Short: "Delete a provider", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, ValidArgsFunction: func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetProviderSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, logpkg.Default) }, } deleteCmd.Flags().BoolVar(&cmd.IgnoreNotFound, "ignore-not-found", false, "Treat \"provider not found\" as a successful delete") deleteCmd.Flags().BoolVar(&cmd.Force, "force", false, "Force delete the provider and ignore provider is already used") _ = deleteCmd.Flags().MarkHidden("force") return deleteCmd } func (cmd *DeleteCmd) Run(ctx context.Context, args []string) error { if len(args) > 1 { return fmt.Errorf("please specify a provider to delete") } devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } provider := devPodConfig.Current().DefaultProvider if len(args) > 0 { provider = args[0] } else if provider == "" { return fmt.Errorf("please specify a provider to delete") } // delete the provider err = DeleteProvider(ctx, devPodConfig, provider, cmd.IgnoreNotFound, cmd.Force, logpkg.Default) if err != nil { return err } logpkg.Default.Donef("Successfully deleted provider '%s'", provider) return nil } func DeleteProvider(ctx context.Context, devPodConfig *config.Config, provider string, ignoreNotFound, force bool, log logpkg.Logger) error { // if force is not set, check if the provider is associated with a pro instance or workspace if !force { // check if this provider is associated with a pro instance proInstances, err := workspace.ListProInstances(devPodConfig, logpkg.Default) if err != nil { return fmt.Errorf("list pro instances: %w", err) } for _, instance := range proInstances { if instance.Provider == provider { return fmt.Errorf("cannot delete provider '%s', because it is connected to Pro instance '%s'. Removing the Pro instance will automatically delete this provider", instance.Provider, instance.Host) } } // check if there are workspaces that still use this provider workspaces, err := workspace.List(ctx, devPodConfig, true, platform.AllOwnerFilter, log) if err != nil { return err } // search for workspace that uses this machine for _, workspace := range workspaces { if workspace.Provider.Name == provider { return fmt.Errorf("cannot delete provider '%s', because workspace '%s' is still using it. Please delete the workspace '%s' before deleting the provider", workspace.Provider.Name, workspace.ID, workspace.ID) } } } return DeleteProviderConfig(devPodConfig, provider, ignoreNotFound) } func DeleteProviderConfig(devPodConfig *config.Config, provider string, ignoreNotFound bool) error { if devPodConfig.Current().DefaultProvider == provider { devPodConfig.Current().DefaultProvider = "" } delete(devPodConfig.Current().Providers, provider) err := config.SaveConfig(devPodConfig) if err != nil { return fmt.Errorf("save config: %w", err) } providerDir, err := provider2.GetProviderDir(devPodConfig.DefaultContext, provider) if err != nil { return err } _, err = os.Stat(providerDir) if err != nil { if os.IsNotExist(err) { if ignoreNotFound { return nil } return fmt.Errorf("provider '%s' does not exist", provider) } return err } err = os.RemoveAll(providerDir) if err != nil { return fmt.Errorf("delete provider dir: %w", err) } return nil } ================================================ FILE: cmd/provider/list-default-providers.go ================================================ package provider import ( "context" "encoding/json" "fmt" "io" "net/http" "strings" "github.com/loft-sh/devpod/cmd/flags" devpodhttp "github.com/loft-sh/devpod/pkg/http" "github.com/spf13/cobra" ) // ListAvailableCmd holds the list cmd flags type ListAvailableCmd struct { flags.GlobalFlags } func getDevpodProviderList() error { req, err := http.NewRequest("GET", "https://api.github.com/users/loft-sh/repos", nil) if err != nil { return err } resp, err := devpodhttp.GetHTTPClient().Do(req) if err != nil { return err } defer resp.Body.Close() result, err := io.ReadAll(resp.Body) if err != nil { return err } var jsonResult []map[string]interface{} err = json.Unmarshal(result, &jsonResult) if err != nil { return err } fmt.Println("List of available providers from loft:") for _, v := range jsonResult { if strings.Contains(v["name"].(string), "devpod-provider") { name := strings.TrimPrefix(v["name"].(string), "devpod-provider-") fmt.Println("\t", name) } } return nil } // NewListAvailableCmd creates a new command func NewListAvailableCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &ListAvailableCmd{ GlobalFlags: *flags, } listAvailableCmd := &cobra.Command{ Use: "list-available", Short: "List providers available for installation", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background()) }, } return listAvailableCmd } // Run runs the command logic func (cmd *ListAvailableCmd) Run(ctx context.Context) error { return getDevpodProviderList() } ================================================ FILE: cmd/provider/list.go ================================================ package provider import ( "context" "encoding/json" "fmt" "sort" "strconv" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/types" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/loft-sh/log/table" "github.com/spf13/cobra" ) // ListCmd holds the list cmd flags type ListCmd struct { flags.GlobalFlags Output string } // NewListCmd creates a new command func NewListCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &ListCmd{ GlobalFlags: *flags, } listCmd := &cobra.Command{ Use: "list", Aliases: []string{"ls"}, Short: "List available providers", Args: cobra.NoArgs, RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background()) }, } listCmd.Flags().StringVar(&cmd.Output, "output", "plain", "The output format to use. Can be json or plain") return listCmd } type ProviderWithDefault struct { workspace.ProviderWithOptions `json:",inline"` Default bool `json:"default,omitempty"` } // Run runs the command logic func (cmd *ListCmd) Run(ctx context.Context) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } providers, err := workspace.LoadAllProviders(devPodConfig, log.Default.ErrorStreamOnly()) if err != nil { return err } configuredProviders := devPodConfig.Current().Providers if configuredProviders == nil { configuredProviders = map[string]*config.ProviderConfig{} } if cmd.Output == "plain" { tableEntries := [][]string{} for _, entry := range providers { tableEntries = append(tableEntries, []string{ entry.Config.Name, entry.Config.Version, strconv.FormatBool(devPodConfig.Current().DefaultProvider == entry.Config.Name), strconv.FormatBool(entry.State != nil && entry.State.Initialized), entry.Config.Description, }) } sort.SliceStable(tableEntries, func(i, j int) bool { return tableEntries[i][0] < tableEntries[j][0] }) table.PrintTable(log.Default, []string{ "Name", "Version", "Default", "Initialized", "Description", }, tableEntries) } else if cmd.Output == "json" { retMap := map[string]ProviderWithDefault{} for k, entry := range providers { var dynamicOptions map[string]*types.Option if configuredProviders[entry.Config.Name] != nil { dynamicOptions = configuredProviders[entry.Config.Name].DynamicOptions } srcOptions := MergeDynamicOptions(entry.Config.Options, dynamicOptions) entry.Config.Options = srcOptions retMap[k] = ProviderWithDefault{ ProviderWithOptions: *entry, Default: devPodConfig.Current().DefaultProvider == entry.Config.Name, } } out, err := json.MarshalIndent(retMap, "", " ") if err != nil { return err } fmt.Print(string(out)) } else { return fmt.Errorf("unexpected output format, choose either json or plain. Got %s", cmd.Output) } return nil } ================================================ FILE: cmd/provider/options.go ================================================ package provider import ( "context" "encoding/json" "fmt" "sort" "strconv" "github.com/loft-sh/devpod/cmd/completion" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/types" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/loft-sh/log/table" "github.com/spf13/cobra" ) // OptionsCmd holds the options cmd flags type OptionsCmd struct { *flags.GlobalFlags Hidden bool Output string } // NewOptionsCmd creates a new command func NewOptionsCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &OptionsCmd{ GlobalFlags: flags, } optionsCmd := &cobra.Command{ Use: "options [provider]", Short: "Show options of an existing provider", RunE: func(_ *cobra.Command, args []string) error { return cmd.Run(context.Background(), args) }, ValidArgsFunction: func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetProviderSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, log.Default) }, } optionsCmd.Flags().BoolVar(&cmd.Hidden, "hidden", false, "If true, will also show hidden options.") optionsCmd.Flags().StringVar(&cmd.Output, "output", "plain", "The output format to use. Can be json or plain") return optionsCmd } type optionWithValue struct { types.Option `json:",inline"` Children []string `json:"children,omitempty"` Value string `json:"value,omitempty"` } // Run runs the command logic func (cmd *OptionsCmd) Run(ctx context.Context, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } providerName := devPodConfig.Current().DefaultProvider if len(args) > 0 { providerName = args[0] } else if providerName == "" { return fmt.Errorf("please specify a provider") } if providerName != "" && cmd.GlobalFlags.Provider != "" { if providerName != cmd.GlobalFlags.Provider { log.Default.Infof("providerName=%+v", providerName) log.Default.Infof("GlobalFlags.Provider=%+v", cmd.GlobalFlags.Provider) return fmt.Errorf("ambiguous provider configuration detected") } } providerWithOptions, err := workspace.FindProvider(devPodConfig, providerName, log.Default.ErrorStreamOnly()) if err != nil { return err } return printOptions(devPodConfig, providerWithOptions, cmd.Output, cmd.Hidden) } func printOptions(devPodConfig *config.Config, provider *workspace.ProviderWithOptions, format string, showHidden bool) error { entryOptions := devPodConfig.ProviderOptions(provider.Config.Name) dynamicOptions := devPodConfig.DynamicProviderOptionDefinitions(provider.Config.Name) srcOptions := MergeDynamicOptions(provider.Config.Options, dynamicOptions) if format == "plain" { tableEntries := [][]string{} for optionName, entry := range srcOptions { if !showHidden && entry.Hidden { continue } value := entryOptions[optionName].Value if value != "" && entry.Password { value = "********" } tableEntries = append(tableEntries, []string{ optionName, strconv.FormatBool(entry.Required), entry.Description, entry.Default, value, }) } sort.SliceStable(tableEntries, func(i, j int) bool { return tableEntries[i][0] < tableEntries[j][0] }) table.PrintTable(log.Default, []string{ "Name", "Required", "Description", "Default", "Value", }, tableEntries) } else if format == "json" { options := map[string]optionWithValue{} for optionName, entry := range srcOptions { if !showHidden && entry.Hidden { continue } options[optionName] = optionWithValue{ Option: *entry, Children: entryOptions[optionName].Children, Value: entryOptions[optionName].Value, } } out, err := json.MarshalIndent(options, "", " ") if err != nil { return err } fmt.Print(string(out)) } else { return fmt.Errorf("unexpected output format, choose either json or plain. Got %s", format) } return nil } // MergeDynamicOptions merges the static provider options and dynamic options func MergeDynamicOptions(options map[string]*types.Option, dynamicOptions config.OptionDefinitions) map[string]*types.Option { retOptions := map[string]*types.Option{} for k, option := range options { retOptions[k] = option } for k, option := range dynamicOptions { retOptions[k] = option } return retOptions } ================================================ FILE: cmd/provider/provider.go ================================================ package provider import ( "github.com/loft-sh/devpod/cmd/flags" "github.com/spf13/cobra" ) // NewProviderCmd returns a new root command func NewProviderCmd(flags *flags.GlobalFlags) *cobra.Command { providerCmd := &cobra.Command{ Use: "provider", Short: "DevPod Provider commands", } providerCmd.AddCommand(NewListCmd(flags)) providerCmd.AddCommand(NewListAvailableCmd(flags)) providerCmd.AddCommand(NewUseCmd(flags)) providerCmd.AddCommand(NewOptionsCmd(flags)) providerCmd.AddCommand(NewDeleteCmd(flags)) providerCmd.AddCommand(NewAddCmd(flags)) providerCmd.AddCommand(NewUpdateCmd(flags)) providerCmd.AddCommand(NewSetOptionsCmd(flags)) return providerCmd } ================================================ FILE: cmd/provider/set_options.go ================================================ package provider import ( "context" "fmt" "os" "github.com/loft-sh/devpod/cmd/completion" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" ) // SetOptionsCmd holds the use cmd flags type SetOptionsCmd struct { flags.GlobalFlags Dry bool Reconfigure bool SingleMachine bool Options []string } // NewSetOptionsCmd creates a new command func NewSetOptionsCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &SetOptionsCmd{ GlobalFlags: *flags, } setOptionsCmd := &cobra.Command{ Use: "set-options [provider]", Short: "Sets options for the given provider. Similar to 'devpod provider use', but does not switch the default provider.", RunE: func(_ *cobra.Command, args []string) error { logger := log.Logger(log.Default) if cmd.Dry { logger = log.Default.ErrorStreamOnly() } return cmd.Run(context.Background(), args, logger) }, ValidArgsFunction: func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetProviderSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, log.Default) }, } setOptionsCmd.Flags().BoolVar(&cmd.SingleMachine, "single-machine", false, "If enabled will use a single machine for all workspaces") setOptionsCmd.Flags().BoolVar(&cmd.Reconfigure, "reconfigure", false, "If enabled will not merge existing provider config") setOptionsCmd.Flags().StringArrayVarP(&cmd.Options, "option", "o", []string{}, "Provider option in the form KEY=VALUE") setOptionsCmd.Flags().BoolVar(&cmd.Dry, "dry", false, "Dry will not persist the options to file and instead return the new filled options") return setOptionsCmd } // Run runs the command logic func (cmd *SetOptionsCmd) Run(ctx context.Context, args []string, log log.Logger) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } providerName := devPodConfig.Current().DefaultProvider if len(args) > 0 { providerName = args[0] } else if providerName == "" { return fmt.Errorf("please specify a provider") } log.Debugf("providerName=%+v", providerName) if os.Getenv("DEVPOD_UI") == "" && len(cmd.Options) == 0 { return fmt.Errorf("please specify option") } log.Debugf("Options=%+v", cmd.Options) providerWithOptions, err := workspace.FindProvider(devPodConfig, providerName, log) if err != nil { return err } devPodConfig, err = setOptions( ctx, providerWithOptions.Config, devPodConfig.DefaultContext, cmd.Options, cmd.Reconfigure, cmd.Dry, cmd.Dry, false, &cmd.SingleMachine, log, ) if err != nil { return err } // save provider config if !cmd.Dry { err = config.SaveConfig(devPodConfig) if err != nil { return errors.Wrap(err, "save config") } } else { // print options to stdout err = printOptions(devPodConfig, providerWithOptions, "json", true) if err != nil { return fmt.Errorf("print options: %w", err) } } // print success message log.Donef("Successfully set options for provider '%s'", providerWithOptions.Config.Name) return nil } ================================================ FILE: cmd/provider/update.go ================================================ package provider import ( "context" "fmt" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" ) // UpdateCmd holds the cmd flags type UpdateCmd struct { *flags.GlobalFlags Use bool Options []string } // NewUpdateCmd creates a new command func NewUpdateCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &UpdateCmd{ GlobalFlags: flags, } updateCmd := &cobra.Command{ Use: "update [name] [URL or path]", Short: "Updates a provider in DevPod", RunE: func(_ *cobra.Command, args []string) error { ctx := context.Background() devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } return cmd.Run(ctx, devPodConfig, args) }, } updateCmd.Flags().BoolVar(&cmd.Use, "use", true, "If enabled will automatically activate the provider") updateCmd.Flags().StringArrayVarP(&cmd.Options, "option", "o", []string{}, "Provider option in the form KEY=VALUE") return updateCmd } func (cmd *UpdateCmd) Run(ctx context.Context, devPodConfig *config.Config, args []string) error { if len(args) != 1 && len(args) != 2 { return fmt.Errorf("please specify either a local file, url or git repository. E.g. devpod provider update my-provider loft-sh/devpod-provider-gcloud") } providerSource := "" if len(args) == 2 { providerSource = args[1] } providerConfig, err := workspace.UpdateProvider(devPodConfig, args[0], providerSource, log.Default) if err != nil { return err } log.Default.Donef("Successfully updated provider %s", providerConfig.Name) if cmd.Use { err = ConfigureProvider(ctx, providerConfig, devPodConfig.DefaultContext, cmd.Options, false, false, false, nil, log.Default) if err != nil { log.Default.Errorf("Error configuring provider, please retry with 'devpod provider use %s --reconfigure'", providerConfig.Name) return errors.Wrap(err, "configure provider") } return nil } log.Default.Infof("To use the provider, please run the following command:") log.Default.Infof("devpod provider use %s", providerConfig.Name) return nil } ================================================ FILE: cmd/provider/use.go ================================================ package provider import ( "context" "fmt" "io" "github.com/loft-sh/devpod/cmd/completion" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" options2 "github.com/loft-sh/devpod/pkg/options" provider2 "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) // UseCmd holds the use cmd flags type UseCmd struct { flags.GlobalFlags Reconfigure bool SingleMachine bool Options []string // only for testing SkipInit bool } // NewUseCmd creates a new command func NewUseCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &UseCmd{ GlobalFlags: *flags, } useCmd := &cobra.Command{ Use: "use [name]", Short: "Configure an existing provider and set as default", RunE: func(_ *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("please specify the provider to use") } return cmd.Run(context.Background(), args[0]) }, ValidArgsFunction: func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetProviderSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, log.Default) }, } AddFlags(useCmd, cmd) return useCmd } func AddFlags(useCmd *cobra.Command, cmd *UseCmd) { useCmd.Flags().BoolVar(&cmd.SingleMachine, "single-machine", false, "If enabled will use a single machine for all workspaces") useCmd.Flags().BoolVar(&cmd.Reconfigure, "reconfigure", false, "If enabled will not merge existing provider config") useCmd.Flags().StringArrayVarP(&cmd.Options, "option", "o", []string{}, "Provider option in the form KEY=VALUE") useCmd.Flags().BoolVar(&cmd.SkipInit, "skip-init", false, "ONLY FOR TESTING: If true will skip init") _ = useCmd.Flags().MarkHidden("skip-init") } // Run runs the command logic func (cmd *UseCmd) Run(ctx context.Context, providerName string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } providerWithOptions, err := workspace.FindProvider(devPodConfig, providerName, log.Default) if err != nil { return err } // should reconfigure? shouldReconfigure := cmd.Reconfigure || len(cmd.Options) > 0 || providerWithOptions.State == nil || cmd.SingleMachine if shouldReconfigure { return ConfigureProvider(ctx, providerWithOptions.Config, devPodConfig.DefaultContext, cmd.Options, cmd.Reconfigure, cmd.SkipInit, false, &cmd.SingleMachine, log.Default) } else { log.Default.Infof("To reconfigure provider %s, run with '--reconfigure' to reconfigure the provider", providerWithOptions.Config.Name) } // set options defaultContext := devPodConfig.Current() defaultContext.DefaultProvider = providerWithOptions.Config.Name // save provider config err = config.SaveConfig(devPodConfig) if err != nil { return errors.Wrap(err, "save config") } // print success message log.Default.Donef("Successfully switched default provider to '%s'", providerWithOptions.Config.Name) return nil } func ConfigureProvider(ctx context.Context, provider *provider2.ProviderConfig, context string, userOptions []string, reconfigure, skipInit, skipSubOptions bool, singleMachine *bool, log log.Logger) error { // set options devPodConfig, err := setOptions(ctx, provider, context, userOptions, reconfigure, false, skipInit, skipSubOptions, singleMachine, log) if err != nil { return err } // set options defaultContext := devPodConfig.Current() defaultContext.DefaultProvider = provider.Name // save provider config err = config.SaveConfig(devPodConfig) if err != nil { return errors.Wrap(err, "save config") } log.Donef("Successfully configured provider '%s'", provider.Name) return nil } func setOptions( ctx context.Context, provider *provider2.ProviderConfig, context string, userOptions []string, reconfigure, skipRequired, skipInit, skipSubOptions bool, singleMachine *bool, log log.Logger, ) (*config.Config, error) { devPodConfig, err := config.LoadConfig(context, "") if err != nil { return nil, err } // parse options options, err := provider2.ParseOptions(userOptions) if err != nil { return nil, errors.Wrap(err, "parse options") } // merge with old values if !reconfigure { for k, v := range devPodConfig.ProviderOptions(provider.Name) { _, ok := options[k] if !ok && v.UserProvided { options[k] = v.Value } } } // fill defaults devPodConfig, err = options2.ResolveOptions(ctx, devPodConfig, provider, options, skipRequired, skipSubOptions, singleMachine, log) if err != nil { return nil, errors.Wrap(err, "resolve options") } // run init command if !skipInit { stdout := log.Writer(logrus.InfoLevel, false) defer stdout.Close() stderr := log.Writer(logrus.ErrorLevel, false) defer stderr.Close() err = initProvider(ctx, devPodConfig, provider, stdout, stderr) if err != nil { return nil, err } } return devPodConfig, nil } func initProvider(ctx context.Context, devPodConfig *config.Config, provider *provider2.ProviderConfig, stdout, stderr io.Writer) error { // run init command err := clientimplementation.RunCommandWithBinaries( ctx, "init", provider.Exec.Init, devPodConfig.DefaultContext, nil, nil, devPodConfig.ProviderOptions(provider.Name), provider, nil, nil, stdout, stderr, log.Default, ) if err != nil { return errors.Wrap(err, "init") } if devPodConfig.Current().Providers == nil { devPodConfig.Current().Providers = map[string]*config.ProviderConfig{} } if devPodConfig.Current().Providers[provider.Name] == nil { devPodConfig.Current().Providers[provider.Name] = &config.ProviderConfig{} } devPodConfig.Current().Providers[provider.Name].Initialized = true return nil } ================================================ FILE: cmd/root.go ================================================ package cmd import ( "fmt" "os" "os/exec" "github.com/loft-sh/devpod/cmd/agent" "github.com/loft-sh/devpod/cmd/completion" "github.com/loft-sh/devpod/cmd/context" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/cmd/helper" "github.com/loft-sh/devpod/cmd/ide" "github.com/loft-sh/devpod/cmd/machine" "github.com/loft-sh/devpod/cmd/pro" "github.com/loft-sh/devpod/cmd/provider" "github.com/loft-sh/devpod/cmd/use" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" "github.com/loft-sh/devpod/pkg/telemetry" log2 "github.com/loft-sh/log" "github.com/loft-sh/log/terminal" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" ) var globalFlags *flags.GlobalFlags // NewRootCmd returns a new root command func NewRootCmd() *cobra.Command { return &cobra.Command{ Use: "devpod", Short: "DevPod", SilenceUsage: true, SilenceErrors: true, PersistentPreRunE: func(cobraCmd *cobra.Command, args []string) error { if globalFlags.LogOutput == "json" { log2.Default.SetFormat(log2.JSONFormat) } else if globalFlags.LogOutput == "raw" { log2.Default.SetFormat(log2.RawFormat) } else if globalFlags.LogOutput != "plain" { return fmt.Errorf("unrecognized log format %s, needs to be either plain or json", globalFlags.LogOutput) } if globalFlags.Silent { log2.Default.SetLevel(logrus.FatalLevel) } else if globalFlags.Debug { log2.Default.SetLevel(logrus.DebugLevel) } else if os.Getenv(clientimplementation.DevPodDebug) == "true" { log2.Default.SetLevel(logrus.DebugLevel) } if globalFlags.DevPodHome != "" { _ = os.Setenv(config.DEVPOD_HOME, globalFlags.DevPodHome) } devPodConfig, err := config.LoadConfig(globalFlags.Context, globalFlags.Provider) if err == nil { telemetry.StartCLI(devPodConfig, cobraCmd) } return nil }, PersistentPostRunE: func(cmd *cobra.Command, args []string) error { if globalFlags.DevPodHome != "" { _ = os.Unsetenv(config.DEVPOD_HOME) } return nil }, } } // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { // build the root command rootCmd := BuildRoot() // execute command err := rootCmd.Execute() telemetry.CollectorCLI.RecordCLI(err) telemetry.CollectorCLI.Flush() if err != nil { //nolint:all if sshExitErr, ok := err.(*ssh.ExitError); ok { os.Exit(sshExitErr.ExitStatus()) } //nolint:all if execExitErr, ok := err.(*exec.ExitError); ok { os.Exit(execExitErr.ExitCode()) } if globalFlags.Debug { log2.Default.Fatalf("%+v", err) } else { if rootCmd.Annotations == nil || rootCmd.Annotations[agent.AgentExecutedAnnotation] != "true" { if terminal.IsTerminalIn { log2.Default.Error("Try using the --debug flag to see a more verbose output") } else if os.Getenv(telemetry.UIEnvVar) == "true" { log2.Default.Error("Try enabling Debug mode under Settings to see a more verbose output") } } log2.Default.Fatal(err) } } } // BuildRoot creates a new root command from the func BuildRoot() *cobra.Command { rootCmd := NewRootCmd() persistentFlags := rootCmd.PersistentFlags() globalFlags = flags.SetGlobalFlags(persistentFlags) _ = completion.RegisterFlagCompletionFuns(rootCmd, globalFlags) rootCmd.AddCommand(agent.NewAgentCmd(globalFlags)) rootCmd.AddCommand(provider.NewProviderCmd(globalFlags)) rootCmd.AddCommand(use.NewUseCmd(globalFlags)) rootCmd.AddCommand(helper.NewHelperCmd(globalFlags)) rootCmd.AddCommand(ide.NewIDECmd(globalFlags)) rootCmd.AddCommand(machine.NewMachineCmd(globalFlags)) rootCmd.AddCommand(context.NewContextCmd(globalFlags)) rootCmd.AddCommand(pro.NewProCmd(globalFlags, log2.Default)) rootCmd.AddCommand(NewUpCmd(globalFlags)) rootCmd.AddCommand(NewDeleteCmd(globalFlags)) rootCmd.AddCommand(NewSSHCmd(globalFlags)) rootCmd.AddCommand(NewVersionCmd()) rootCmd.AddCommand(NewStopCmd(globalFlags)) rootCmd.AddCommand(NewListCmd(globalFlags)) rootCmd.AddCommand(NewStatusCmd(globalFlags)) rootCmd.AddCommand(NewBuildCmd(globalFlags)) rootCmd.AddCommand(NewLogsDaemonCmd(globalFlags)) rootCmd.AddCommand(NewExportCmd(globalFlags)) rootCmd.AddCommand(NewImportCmd(globalFlags)) rootCmd.AddCommand(NewLogsCmd(globalFlags)) rootCmd.AddCommand(NewUpgradeCmd()) rootCmd.AddCommand(NewTroubleshootCmd(globalFlags)) rootCmd.AddCommand(NewPingCmd(globalFlags)) return rootCmd } ================================================ FILE: cmd/ssh.go ================================================ package cmd import ( "context" "encoding/base64" "fmt" "io" "os" "os/exec" "path/filepath" "strings" "time" "github.com/loft-sh/devpod/cmd/completion" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/cmd/machine" "github.com/loft-sh/devpod/pkg/agent" client2 "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/config" daemon "github.com/loft-sh/devpod/pkg/daemon/platform" "github.com/loft-sh/devpod/pkg/gpg" "github.com/loft-sh/devpod/pkg/port" "github.com/loft-sh/devpod/pkg/provider" devssh "github.com/loft-sh/devpod/pkg/ssh" "github.com/loft-sh/devpod/pkg/tunnel" workspace2 "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" ) const ( DisableSSHKeepAlive time.Duration = 0 * time.Second ) // SSHCmd holds the ssh cmd flags type SSHCmd struct { *flags.GlobalFlags ForwardPortsTimeout string ForwardPorts []string ReverseForwardPorts []string SendEnvVars []string SetEnvVars []string Stdio bool JumpContainer bool ReuseSSHAuthSock string AgentForwarding bool GPGAgentForwarding bool GitSSHSignatureForwarding bool // ssh keepalive options SSHKeepAliveInterval time.Duration `json:"sshKeepAliveInterval,omitempty"` StartServices bool Command string User string WorkDir string } // NewSSHCmd creates a new ssh command func NewSSHCmd(f *flags.GlobalFlags) *cobra.Command { cmd := &SSHCmd{ GlobalFlags: f, } sshCmd := &cobra.Command{ Use: "ssh [flags] [workspace-folder|workspace-name]", Short: "Starts a new ssh session to a workspace", RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } localOnly := false if cmd.Stdio { localOnly = true } ctx := cobraCmd.Context() client, err := workspace2.Get(ctx, devPodConfig, args, true, cmd.Owner, localOnly, log.Default.ErrorStreamOnly()) if err != nil { return err } return cmd.Run(ctx, devPodConfig, client, log.Default.ErrorStreamOnly()) }, ValidArgsFunction: func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetWorkspaceSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, log.Default) }, } sshCmd.Flags().StringArrayVarP(&cmd.ForwardPorts, "forward-ports", "L", []string{}, "Specifies that connections to the given TCP port or Unix socket on the local (client) host are to be forwarded to the given host and port, or Unix socket, on the remote side.") sshCmd.Flags().StringArrayVarP(&cmd.ReverseForwardPorts, "reverse-forward-ports", "R", []string{}, "Specifies that connections to the given TCP port or Unix socket on the local (client) host are to be reverse forwarded to the given host and port, or Unix socket, on the remote side.") sshCmd.Flags().StringArrayVarP(&cmd.SendEnvVars, "send-env", "", []string{}, "Specifies which local env variables shall be sent to the container.") sshCmd.Flags().StringArrayVarP(&cmd.SetEnvVars, "set-env", "", []string{}, "Specifies env variables to be set in the container.") sshCmd.Flags().StringVar(&cmd.ForwardPortsTimeout, "forward-ports-timeout", "", "Specifies the timeout after which the command should terminate when the ports are unused.") sshCmd.Flags().StringVar(&cmd.Command, "command", "", "The command to execute within the workspace") sshCmd.Flags().StringVar(&cmd.User, "user", "", "The user of the workspace to use") sshCmd.Flags().StringVar(&cmd.WorkDir, "workdir", "", "The working directory in the container") sshCmd.Flags().BoolVar(&cmd.AgentForwarding, "agent-forwarding", true, "If true forward the local ssh keys to the remote machine") sshCmd.Flags().StringVar(&cmd.ReuseSSHAuthSock, "reuse-ssh-auth-sock", "", "If set, the SSH_AUTH_SOCK is expected to already be available in the workspace (under /tmp using the key provided) and the connection reuses this instead of creating a new one") _ = sshCmd.Flags().MarkHidden("reuse-ssh-auth-sock") sshCmd.Flags().BoolVar(&cmd.GPGAgentForwarding, "gpg-agent-forwarding", false, "If true forward the local gpg-agent to the remote machine") sshCmd.Flags().BoolVar(&cmd.Stdio, "stdio", false, "If true will tunnel connection through stdout and stdin") sshCmd.Flags().BoolVar(&cmd.StartServices, "start-services", true, "If false will not start any port-forwarding or git / docker credentials helper") sshCmd.Flags().DurationVar(&cmd.SSHKeepAliveInterval, "ssh-keepalive-interval", 55*time.Second, "How often should keepalive request be made (55s)") return sshCmd } // Run runs the command logic func (cmd *SSHCmd) Run( ctx context.Context, devPodConfig *config.Config, client client2.BaseWorkspaceClient, log log.Logger) error { // add ssh keys to agent if devPodConfig.ContextOption(config.ContextOptionSSHAgentForwarding) == "true" && devPodConfig.ContextOption(config.ContextOptionSSHAddPrivateKeys) == "true" { log.Debug("Adding ssh keys to agent, disable via 'devpod context set-options -o SSH_ADD_PRIVATE_KEYS=false'") err := devssh.AddPrivateKeysToAgent(ctx, log) if err != nil { log.Debugf("Error adding private keys to ssh-agent: %v", err) } } // get user if cmd.User == "" { var err error cmd.User, err = devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath) if err != nil { return err } } // set default context if needed if cmd.Context == "" { cmd.Context = devPodConfig.DefaultContext } workspaceClient, ok := client.(client2.WorkspaceClient) if ok { return cmd.jumpContainer(ctx, devPodConfig, workspaceClient, log) } proxyClient, ok := client.(client2.ProxyClient) if ok { return cmd.startProxyTunnel(ctx, devPodConfig, proxyClient, log) } daemonClient, ok := client.(client2.DaemonClient) if ok { return cmd.jumpContainerTailscale(ctx, devPodConfig, daemonClient, log) } return nil } func (cmd *SSHCmd) jumpContainerTailscale( ctx context.Context, devPodConfig *config.Config, client client2.DaemonClient, log log.Logger, ) error { log.Debugf("Starting tailscale connection") err := client.CheckWorkspaceReachable(ctx) if err != nil { return err } toolSSHClient, sshClient, err := client.SSHClients(ctx, cmd.User) if err != nil { return err } defer toolSSHClient.Close() defer sshClient.Close() // Forward ports if specified if len(cmd.ForwardPorts) > 0 { return cmd.forwardPorts(ctx, toolSSHClient, log) } // Reverse forward ports if specified if len(cmd.ReverseForwardPorts) > 0 && !cmd.GPGAgentForwarding { return cmd.reverseForwardPorts(ctx, toolSSHClient, log) } if cmd.StartServices { go func() { err = startServicesDaemon(ctx, devPodConfig, client, toolSSHClient, cmd.User, log, false, nil, ) if err != nil { log.Errorf("Error starting services: %v", err) } }() } // Handle GPG agent forwarding if cmd.GPGAgentForwarding || devPodConfig.ContextOption(config.ContextOptionGPGAgentForwarding) == "true" { if gpg.IsGpgTunnelRunning(cmd.User, ctx, toolSSHClient, log) { log.Debugf("[GPG] exporting already running, skipping") } else if err := cmd.setupGPGAgent(ctx, toolSSHClient, log); err != nil { return err } } // Handle ssh stdio mode if cmd.Stdio { if cmd.SSHKeepAliveInterval != DisableSSHKeepAlive { go startSSHKeepAlive(ctx, toolSSHClient, cmd.SSHKeepAliveInterval, log) } return client.DirectTunnel(ctx, os.Stdin, os.Stdout) } // Connect to the inner server and handle user session return machine.RunSSHSession( ctx, sshClient, cmd.AgentForwarding, cmd.Command, os.Stderr, ) } func (cmd *SSHCmd) startProxyTunnel( ctx context.Context, devPodConfig *config.Config, client client2.ProxyClient, log log.Logger, ) error { log.Debugf("Start proxy tunnel") return tunnel.NewTunnel( ctx, func(ctx context.Context, stdin io.Reader, stdout io.Writer) error { return client.Ssh(ctx, client2.SshOptions{ User: cmd.User, Stdin: stdin, Stdout: stdout, }) }, func(ctx context.Context, containerClient *ssh.Client) error { return cmd.startTunnel(ctx, devPodConfig, containerClient, client, log) }, ) } func startWait( ctx context.Context, client client2.WorkspaceClient, create bool, log log.Logger, ) error { startWaiting := time.Now() for { instanceStatus, err := client.Status(ctx, client2.StatusOptions{}) if err != nil { return err } else if instanceStatus == client2.StatusBusy { if time.Since(startWaiting) > time.Second*10 { log.Infof("Waiting for workspace to come up...") log.Debugf("Got status %s, expected: Running", instanceStatus) startWaiting = time.Now() } time.Sleep(time.Second * 2) continue } else if instanceStatus == client2.StatusStopped { if create { // start environment err = client.Start(ctx, client2.StartOptions{}) if err != nil { return errors.Wrap(err, "start workspace") } } else { return fmt.Errorf("DevPod workspace is stopped") } } else if instanceStatus == client2.StatusNotFound { if create { // create environment err = client.Create(ctx, client2.CreateOptions{}) if err != nil { return err } } else { return fmt.Errorf("DevPod workspace wasn't found") } } return nil } } func (cmd *SSHCmd) retrieveEnVars() (map[string]string, error) { envVars := make(map[string]string) for _, envVar := range cmd.SendEnvVars { envVarValue, exist := os.LookupEnv(envVar) if exist { envVars[envVar] = envVarValue } } for _, envVar := range cmd.SetEnvVars { parts := strings.Split(envVar, "=") if len(parts) != 2 { return nil, fmt.Errorf("invalid env var: %s", envVar) } envVars[parts[0]] = parts[1] } return envVars, nil } func (cmd *SSHCmd) jumpContainer( ctx context.Context, devPodConfig *config.Config, client client2.WorkspaceClient, log log.Logger, ) error { // lock the workspace as long as we init the connection err := client.Lock(ctx) if err != nil { return err } defer client.Unlock() // start the workspace err = startWait(ctx, client, false, log) if err != nil { return err } envVars, err := cmd.retrieveEnVars() if err != nil { return err } // tunnel to container return tunnel.NewContainerTunnel(client, log). Run(ctx, func(ctx context.Context, containerClient *ssh.Client) error { // we have a connection to the container, make sure others can connect as well client.Unlock() // start ssh tunnel return cmd.startTunnel(ctx, devPodConfig, containerClient, client, log) }, devPodConfig, envVars) } func (cmd *SSHCmd) forwardTimeout(log log.Logger) (time.Duration, error) { timeout := time.Duration(0) if cmd.ForwardPortsTimeout != "" { timeout, err := time.ParseDuration(cmd.ForwardPortsTimeout) if err != nil { return timeout, fmt.Errorf("parse forward ports timeout: %w", err) } log.Infof("Using port forwarding timeout of %s", cmd.ForwardPortsTimeout) } return timeout, nil } func (cmd *SSHCmd) reverseForwardPorts( ctx context.Context, containerClient *ssh.Client, log log.Logger, ) error { timeout, err := cmd.forwardTimeout(log) if err != nil { return fmt.Errorf("parse forward ports timeout: %w", err) } errChan := make(chan error, len(cmd.ReverseForwardPorts)) for _, portMapping := range cmd.ReverseForwardPorts { mapping, err := port.ParsePortSpec(portMapping) if err != nil { return fmt.Errorf("parse port mapping: %w", err) } // start the forwarding log.Infof( "Reverse forwarding local %s/%s to remote %s/%s", mapping.Host.Protocol, mapping.Host.Address, mapping.Container.Protocol, mapping.Container.Address, ) go func(portMapping string) { err := devssh.ReversePortForward( ctx, containerClient, mapping.Host.Protocol, mapping.Host.Address, mapping.Container.Protocol, mapping.Container.Address, timeout, log, ) if !errors.Is(io.EOF, err) { errChan <- fmt.Errorf("error forwarding %s: %w", portMapping, err) } }(portMapping) } return <-errChan } func (cmd *SSHCmd) forwardPorts( ctx context.Context, containerClient *ssh.Client, log log.Logger, ) error { timeout, err := cmd.forwardTimeout(log) if err != nil { return fmt.Errorf("parse forward ports timeout: %w", err) } errChan := make(chan error, len(cmd.ForwardPorts)) for _, portMapping := range cmd.ForwardPorts { mapping, err := port.ParsePortSpec(portMapping) if err != nil { return fmt.Errorf("parse port mapping: %w", err) } // start the forwarding log.Infof( "Forwarding local %s/%s to remote %s/%s", mapping.Host.Protocol, mapping.Host.Address, mapping.Container.Protocol, mapping.Container.Address, ) go func(portMapping string) { err := devssh.PortForward( ctx, containerClient, mapping.Host.Protocol, mapping.Host.Address, mapping.Container.Protocol, mapping.Container.Address, timeout, log, ) if !errors.Is(io.EOF, err) { errChan <- fmt.Errorf("error forwarding %s: %w", portMapping, err) } }(portMapping) } return <-errChan } func (cmd *SSHCmd) startTunnel(ctx context.Context, devPodConfig *config.Config, containerClient *ssh.Client, workspaceClient client2.BaseWorkspaceClient, log log.Logger) error { // check if we should forward ports if len(cmd.ForwardPorts) > 0 { return cmd.forwardPorts(ctx, containerClient, log) } // check if we should reverse forward ports if len(cmd.ReverseForwardPorts) > 0 && !cmd.GPGAgentForwarding { return cmd.reverseForwardPorts(ctx, containerClient, log) } if cmd.StartServices { configureDockerCredentials := devPodConfig.ContextOption(config.ContextOptionSSHInjectDockerCredentials) == "true" configureGitCredentials := devPodConfig.ContextOption(config.ContextOptionSSHInjectGitCredentials) == "true" configureGitSSHSignatureHelper := devPodConfig.ContextOption(config.ContextOptionGitSSHSignatureForwarding) == "true" go cmd.startServices(ctx, devPodConfig, containerClient, workspaceClient.WorkspaceConfig(), configureDockerCredentials, configureGitCredentials, configureGitSSHSignatureHelper, log) } // start ssh writer := log.ErrorStreamOnly().Writer(logrus.InfoLevel, false) defer writer.Close() // check if we should do gpg agent forwarding if cmd.GPGAgentForwarding || devPodConfig.ContextOption(config.ContextOptionGPGAgentForwarding) == "true" { // Check if a forwarding is already enabled and running, in that case // we skip the forwarding and keep using the original one if gpg.IsGpgTunnelRunning(cmd.User, ctx, containerClient, log) { log.Debugf("[GPG] exporting already running, skipping") } else { err := cmd.setupGPGAgent(ctx, containerClient, log) if err != nil { return err } } } workdir := filepath.Join("/workspaces", workspaceClient.Workspace()) if cmd.WorkDir != "" { workdir = cmd.WorkDir } log.Debugf("Run outer container tunnel") command := fmt.Sprintf("'%s' helper ssh-server --track-activity --stdio --workdir '%s'", agent.ContainerDevPodHelperLocation, workdir) if cmd.ReuseSSHAuthSock != "" { log.Debug("Reusing SSH_AUTH_SOCK") command += fmt.Sprintf(" --reuse-ssh-auth-sock=%s", cmd.ReuseSSHAuthSock) } if cmd.Debug { command += " --debug" } if cmd.User != "" && cmd.User != "root" { command = fmt.Sprintf("su -c \"%s\" '%s'", command, cmd.User) } envVars, err := cmd.retrieveEnVars() if err != nil { return err } // Traffic is coming in from the outside, we need to forward it to the container if cmd.Stdio { return devssh.Run(ctx, containerClient, command, os.Stdin, os.Stdout, writer, envVars) } return machine.StartSSHSession( ctx, cmd.User, cmd.Command, cmd.AgentForwarding && devPodConfig.ContextOption(config.ContextOptionSSHAgentForwarding) == "true", func(ctx context.Context, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { if cmd.SSHKeepAliveInterval != DisableSSHKeepAlive { go startSSHKeepAlive(ctx, containerClient, cmd.SSHKeepAliveInterval, log) } return devssh.Run(ctx, containerClient, command, stdin, stdout, stderr, envVars) }, writer, ) } func (cmd *SSHCmd) startServices( ctx context.Context, devPodConfig *config.Config, containerClient *ssh.Client, workspace *provider.Workspace, configureDockerCredentials, configureGitCredentials, configureGitSSHSignatureHelper bool, log log.Logger, ) { if cmd.User != "" { err := tunnel.RunServices( ctx, devPodConfig, containerClient, cmd.User, false, nil, nil, workspace, configureDockerCredentials, configureGitCredentials, configureGitSSHSignatureHelper, log, ) if err != nil { log.Debugf("Error running credential server: %v", err) } } } // setupGPGAgent will forward a local gpg-agent into the remote container // this works by using cmd/agent/workspace/setup_gpg func (cmd *SSHCmd) setupGPGAgent( ctx context.Context, containerClient *ssh.Client, log log.Logger, ) error { log.Debugf("[GPG] exporting gpg owner trust from host") ownerTrustExport, err := gpg.GetHostOwnerTrust() if err != nil { return fmt.Errorf("export local ownertrust from GPG: %w", err) } ownerTrustArgument := base64.StdEncoding.EncodeToString(ownerTrustExport) log.Debugf("[GPG] detecting gpg-agent socket path on host") // Detect local agent extra socket, this will be forwarded to the remote and // symlinked in multiple paths gpgExtraSocketBytes, err := exec.Command("gpgconf", []string{"--list-dir", "agent-extra-socket"}...). Output() if err != nil { return err } gpgExtraSocketPath := strings.TrimSpace(string(gpgExtraSocketBytes)) log.Debugf("[GPG] detected gpg-agent socket path %s", gpgExtraSocketPath) gitGpgKey, err := exec.Command("git", []string{"config", "user.signingKey"}...).Output() if err != nil { log.Debugf("[GPG] no git signkey detected, skipping") } else { log.Debugf("[GPG] detected git sign key %s", gitGpgKey) } cmd.ReverseForwardPorts = append(cmd.ReverseForwardPorts, gpgExtraSocketPath) // Now we forward the agent socket to the remote, and setup remote gpg to use it forwardAgent := []string{ agent.ContainerDevPodHelperLocation, "agent", "workspace", "setup-gpg", "--ownertrust", ownerTrustArgument, "--socketpath", gpgExtraSocketPath, } if log.GetLevel() == logrus.DebugLevel { forwardAgent = append(forwardAgent, "--debug") } if len(gitGpgKey) > 0 { gitKey := strings.TrimSpace(string(gitGpgKey)) forwardAgent = append(forwardAgent, "--gitkey") forwardAgent = append(forwardAgent, fmt.Sprintf("'%s'", gitKey)) } command := strings.Join(forwardAgent, " ") if cmd.User != "" && cmd.User != "root" { command = fmt.Sprintf("su -c \"%s\" '%s'", command, cmd.User) } log.Debugf( "[GPG] start reverse forward of gpg-agent socket %s, keeping connection open", gpgExtraSocketPath, ) go func() { log.Error(cmd.reverseForwardPorts(ctx, containerClient, log)) }() writer := log.ErrorStreamOnly().Writer(logrus.InfoLevel, false) defer writer.Close() err = devssh.Run(ctx, containerClient, command, nil, writer, writer, nil) if err != nil { return fmt.Errorf("run gpg agent setup command: %w", err) } return nil } func startSSHKeepAlive(ctx context.Context, client *ssh.Client, interval time.Duration, log log.Logger) { ticker := time.NewTicker(interval) defer ticker.Stop() for { select { case <-ctx.Done(): return case <-ticker.C: _, _, err := client.SendRequest("keepalive@openssh.com", true, nil) if err != nil { log.Errorf("Failed to send keepalive: %w", err) } } } } func startServicesDaemon(ctx context.Context, devPodConfig *config.Config, client client2.DaemonClient, sshClient *ssh.Client, user string, log log.Logger, forwardPorts bool, extraPorts []string) error { workspace, err := daemon.NewLocalClient(client.Provider()).GetWorkspace(ctx, client.WorkspaceConfig().UID) if err != nil { return err } configureDockerCredentials := devPodConfig.ContextOption(config.ContextOptionSSHInjectDockerCredentials) == "true" configureGitCredentials := devPodConfig.ContextOption(config.ContextOptionSSHInjectGitCredentials) == "true" configureGitSSHSignatureHelper := devPodConfig.ContextOption(config.ContextOptionGitSSHSignatureForwarding) == "true" if workspace != nil && workspace.Status.Instance != nil && workspace.Status.Instance.CredentialForwarding != nil { if workspace.Status.Instance.CredentialForwarding.Docker != nil { configureDockerCredentials = !workspace.Status.Instance.CredentialForwarding.Docker.Disabled } if workspace.Status.Instance.CredentialForwarding.Git != nil { configureGitCredentials = !workspace.Status.Instance.CredentialForwarding.Git.Disabled configureGitSSHSignatureHelper = !workspace.Status.Instance.CredentialForwarding.Git.Disabled } } if user != "" { return tunnel.RunServices( ctx, devPodConfig, sshClient, user, forwardPorts, extraPorts, nil, client.WorkspaceConfig(), configureDockerCredentials, configureGitCredentials, configureGitSSHSignatureHelper, log, ) } return nil } ================================================ FILE: cmd/status.go ================================================ package cmd import ( "context" "encoding/json" "fmt" "time" "github.com/loft-sh/devpod/cmd/completion" "github.com/loft-sh/devpod/cmd/flags" client2 "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" workspace2 "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" ) // StatusCmd holds the cmd flags type StatusCmd struct { *flags.GlobalFlags client2.StatusOptions Output string Timeout string } // NewStatusCmd creates a new command func NewStatusCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &StatusCmd{ GlobalFlags: flags, } statusCmd := &cobra.Command{ Use: "status [flags] [workspace-path|workspace-name]", Short: "Shows the status of a workspace", RunE: func(cobraCmd *cobra.Command, args []string) error { _, err := clientimplementation.DecodeOptionsFromEnv(clientimplementation.DevPodFlagsStatus, &cmd.StatusOptions) if err != nil { return fmt.Errorf("decode status options: %w", err) } ctx := cobraCmd.Context() devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } logger := log.Default.ErrorStreamOnly() client, err := workspace2.Get(ctx, devPodConfig, args, false, cmd.Owner, false, logger) if err != nil { return err } return cmd.Run(ctx, client, logger) }, ValidArgsFunction: func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetWorkspaceSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, log.Default) }, } statusCmd.Flags().BoolVar(&cmd.ContainerStatus, "container-status", true, "If enabled shows the workspace container status as well") statusCmd.Flags().StringVar(&cmd.Output, "output", "plain", "Status shows the workspace status") statusCmd.Flags().StringVar(&cmd.Timeout, "timeout", "30s", "The timeout to wait until the status can be retrieved") return statusCmd } // Run runs the command logic func (cmd *StatusCmd) Run(ctx context.Context, client client2.BaseWorkspaceClient, log log.Logger) error { // parse timeout if cmd.Timeout != "" { duration, err := time.ParseDuration(cmd.Timeout) if err != nil { return errors.Wrap(err, "parse --timeout") } var cancel context.CancelFunc ctx, cancel = context.WithTimeout(ctx, duration) defer cancel() } // get instance status instanceStatus, err := client.Status(ctx, cmd.StatusOptions) if err != nil { return err } if cmd.Output == "plain" { if instanceStatus == client2.StatusStopped { log.Infof("Workspace '%s' is '%s', you can start it via 'devpod up %s'", client.Workspace(), instanceStatus, client.Workspace()) } else if instanceStatus == client2.StatusBusy { log.Infof("Workspace '%s' is '%s', which means its currently unaccessible. This is usually resolved by waiting a couple of minutes", client.Workspace(), instanceStatus) } else if instanceStatus == client2.StatusNotFound { log.Infof("Workspace '%s' is '%s', you can create it via 'devpod up %s'", client.Workspace(), instanceStatus, client.Workspace()) } else { log.Infof("Workspace '%s' is '%s'", client.Workspace(), instanceStatus) } } else if cmd.Output == "json" { out, err := json.Marshal(&client2.WorkspaceStatus{ ID: client.Workspace(), Context: client.Context(), Provider: client.Provider(), State: string(instanceStatus), }) if err != nil { return err } fmt.Print(string(out)) } else { return fmt.Errorf("unexpected output format, choose either json or plain. Got %s", cmd.Output) } return nil } ================================================ FILE: cmd/stop.go ================================================ package cmd import ( "context" "fmt" "github.com/loft-sh/devpod/cmd/completion" "github.com/loft-sh/devpod/cmd/flags" client2 "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/config" workspace2 "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" ) // StopCmd holds the destroy cmd flags type StopCmd struct { *flags.GlobalFlags client2.StopOptions } // NewStopCmd creates a new destroy command func NewStopCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &StopCmd{ GlobalFlags: flags, } stopCmd := &cobra.Command{ Use: "stop [flags] [workspace-path|workspace-name]", Aliases: []string{"down"}, Short: "Stops an existing workspace", RunE: func(cobraCmd *cobra.Command, args []string) error { ctx := cobraCmd.Context() devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } err = clientimplementation.DecodePlatformOptionsFromEnv(&cmd.StopOptions.Platform) if err != nil { return fmt.Errorf("decode platform options: %w", err) } client, err := workspace2.Get(ctx, devPodConfig, args, false, cmd.Owner, false, log.Default) if err != nil { return err } return cmd.Run(ctx, devPodConfig, client) }, ValidArgsFunction: func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetWorkspaceSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, log.Default) }, } return stopCmd } // Run runs the command logic func (cmd *StopCmd) Run(ctx context.Context, devPodConfig *config.Config, client client2.BaseWorkspaceClient) error { // lock workspace if !cmd.Platform.Enabled { err := client.Lock(ctx) if err != nil { return err } defer client.Unlock() } // get instance status instanceStatus, err := client.Status(ctx, client2.StatusOptions{}) if err != nil { return err } else if instanceStatus != client2.StatusRunning { return fmt.Errorf("cannot stop workspace because it is '%s'", instanceStatus) } // stop if single machine provider wasStopped, err := cmd.stopSingleMachine(ctx, client, devPodConfig) if err != nil { return err } else if wasStopped { return nil } // stop environment err = client.Stop(ctx, client2.StopOptions{}) if err != nil { return err } return nil } func (cmd *StopCmd) stopSingleMachine(ctx context.Context, client client2.BaseWorkspaceClient, devPodConfig *config.Config) (bool, error) { // check if single machine singleMachineName := workspace2.SingleMachineName(devPodConfig, client.Provider(), log.Default) if !devPodConfig.Current().IsSingleMachine(client.Provider()) || client.WorkspaceConfig().Machine.ID != singleMachineName { return false, nil } // try to find other workspace with same machine workspaces, err := workspace2.List(ctx, devPodConfig, false, cmd.Owner, log.Default) if err != nil { return false, errors.Wrap(err, "list workspaces") } // loop workspaces foundOther := false for _, workspace := range workspaces { if workspace.ID == client.Workspace() || workspace.Machine.ID != singleMachineName { continue } foundOther = true break } if foundOther { return false, nil } // if we haven't found another workspace on this machine, delete the whole machine machineClient, err := workspace2.GetMachine(devPodConfig, []string{singleMachineName}, log.Default) if err != nil { return false, errors.Wrap(err, "get machine") } // stop the machine err = machineClient.Stop(ctx, client2.StopOptions{}) if err != nil { return false, errors.Wrap(err, "delete machine") } log.Default.Donef("Successfully stopped workspace '%s'", client.Workspace()) return true, nil } ================================================ FILE: cmd/troubleshoot.go ================================================ package cmd import ( "context" "encoding/json" "errors" "fmt" managementv1 "github.com/loft-sh/api/v4/pkg/apis/management/v1" "github.com/loft-sh/devpod/cmd/completion" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/cmd/provider" "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/config" daemon "github.com/loft-sh/devpod/pkg/daemon/platform" "github.com/loft-sh/devpod/pkg/platform" pkgprovider "github.com/loft-sh/devpod/pkg/provider" "github.com/loft-sh/devpod/pkg/version" "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type TroubleshootCmd struct { *flags.GlobalFlags } func NewTroubleshootCmd(flags *flags.GlobalFlags) *cobra.Command { cmd := &TroubleshootCmd{ GlobalFlags: flags, } troubleshootCmd := &cobra.Command{ Use: "troubleshoot [workspace-path|workspace-name]", Short: "Prints the workspaces troubleshooting information", Run: func(cobraCmd *cobra.Command, args []string) { cmd.Run(cobraCmd.Context(), args) }, ValidArgsFunction: func(rootCmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return completion.GetWorkspaceSuggestions(rootCmd, cmd.Context, cmd.Provider, args, toComplete, cmd.Owner, log.Default) }, Hidden: true, } return troubleshootCmd } func (cmd *TroubleshootCmd) Run(ctx context.Context, args []string) { // (ThomasK33): We're creating an anonymous struct here, so that we group // everything and then we can serialize it in one call. var info struct { CLIVersion string Config *config.Config Providers map[string]provider.ProviderWithDefault DevPodProInstances []DevPodProInstance Workspace *pkgprovider.Workspace WorkspaceStatus client.Status WorkspaceTroubleshoot *managementv1.DevPodWorkspaceInstanceTroubleshoot DaemonStatus *daemon.Status Errors []PrintableError `json:",omitempty"` } info.CLIVersion = version.GetVersion() // (ThomasK33): We are defering the printing here, as we want to make sure // that we will always print, even in the case of a panic. defer func() { out, err := json.MarshalIndent(info, "", " ") if err == nil { fmt.Print(string(out)) } else { fmt.Print(err) fmt.Print(info) } }() // NOTE(ThomasK33): Since this is a troubleshooting command, we want to // collect as many relevant information as possible. // For this reason we may not return with an error early. // We are fine with a partially filled TrbouelshootInfo struct, as this // already provides us with more information then before. var err error info.Config, err = config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { info.Errors = append(info.Errors, PrintableError{fmt.Errorf("load config: %w", err)}) // (ThomasK33): It's fine to return early here, as without the devpod config // we cannot do any further troubleshooting. return } logger := log.Default.ErrorStreamOnly() info.Providers, err = collectProviders(info.Config, logger) if err != nil { info.Errors = append(info.Errors, PrintableError{fmt.Errorf("collect providers: %w", err)}) } info.DevPodProInstances, err = collectPlatformInfo(info.Config, logger) if err != nil { info.Errors = append(info.Errors, PrintableError{fmt.Errorf("collect platform info: %w", err)}) } workspaceClient, err := workspace.Get(ctx, info.Config, args, false, cmd.Owner, false, logger) if err == nil { info.Workspace = workspaceClient.WorkspaceConfig() info.WorkspaceStatus, err = workspaceClient.Status(ctx, client.StatusOptions{}) if err != nil { info.Errors = append(info.Errors, PrintableError{fmt.Errorf("workspace status: %w", err)}) } if info.Workspace.Pro != nil { // (ThomasK33): As there can be multiple pro instances configured // we want to iterate over all and find the host that this workspace belongs to. var proInstance DevPodProInstance for _, instance := range info.DevPodProInstances { if instance.ProviderName == info.Workspace.Provider.Name { proInstance = instance break } } if proInstance.ProviderName != "" { info.WorkspaceTroubleshoot, err = collectProWorkspaceInfo( ctx, info.Config, proInstance.Host, logger, info.Workspace.UID, info.Workspace.Pro.Project, ) if err != nil { info.Errors = append(info.Errors, PrintableError{fmt.Errorf("collect pro workspace info: %w", err)}) } } } } else { info.Errors = append(info.Errors, PrintableError{fmt.Errorf("get workspace: %w", err)}) } daemonClient, ok := workspaceClient.(client.DaemonClient) if ok { status, err := daemon.NewLocalClient(daemonClient.Provider()).Status(ctx, true) if err != nil { info.Errors = append(info.Errors, PrintableError{fmt.Errorf("get daemon status: %w", err)}) } else { info.DaemonStatus = &status } } } // collectProWorkspaceInfo collects troubleshooting information for a DevPod Pro instance. // It initializes a client from the host, finds the workspace instance in the project, and retrieves // troubleshooting information using the management client. func collectProWorkspaceInfo( ctx context.Context, devPodConfig *config.Config, host string, logger log.Logger, workspaceUID string, project string, ) (*managementv1.DevPodWorkspaceInstanceTroubleshoot, error) { baseClient, err := platform.InitClientFromHost(ctx, devPodConfig, host, logger) if err != nil { return nil, fmt.Errorf("init client from host: %w", err) } workspace, err := platform.FindInstanceInProject(ctx, baseClient, workspaceUID, project) if err != nil { return nil, err } else if workspace == nil { return nil, fmt.Errorf("couldn't find workspace") } managementClient, err := baseClient.Management() if err != nil { return nil, fmt.Errorf("management: %w", err) } troubleshoot, err := managementClient. Loft(). ManagementV1(). DevPodWorkspaceInstances(workspace.Namespace). Troubleshoot(ctx, workspace.Name, metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("troubleshoot: %w", err) } return troubleshoot, nil } // collectProviders collects and configures providers based on the given devPodConfig. // It returns a map of providers with their default settings and an error if any occurs. func collectProviders(devPodConfig *config.Config, logger log.Logger) (map[string]provider.ProviderWithDefault, error) { providers, err := workspace.LoadAllProviders(devPodConfig, logger) if err != nil { return nil, err } configuredProviders := devPodConfig.Current().Providers if configuredProviders == nil { configuredProviders = map[string]*config.ProviderConfig{} } retMap := map[string]provider.ProviderWithDefault{} for k, entry := range providers { if configuredProviders[entry.Config.Name] == nil { continue } srcOptions := provider.MergeDynamicOptions(entry.Config.Options, configuredProviders[entry.Config.Name].DynamicOptions) entry.Config.Options = srcOptions retMap[k] = provider.ProviderWithDefault{ ProviderWithOptions: *entry, Default: devPodConfig.Current().DefaultProvider == entry.Config.Name, } } return retMap, nil } type DevPodProInstance struct { Host string ProviderName string Version string } // collectPlatformInfo collects information about all platform instances in a given devPodConfig. // It iterates over the pro instances, retrieves their versions, and appends them to the ProInstance slice. // Any errors encountered during this process are combined and returned along with the ProInstance slice. // This means that even when an error value is returned, the pro instance slice will contain valid values. func collectPlatformInfo(devPodConfig *config.Config, logger log.Logger) ([]DevPodProInstance, error) { proInstanceList, err := workspace.ListProInstances(devPodConfig, logger) if err != nil { return nil, fmt.Errorf("list pro instances: %w", err) } var proInstances []DevPodProInstance var combinedErrs error for _, proInstance := range proInstanceList { version, err := platform.GetProInstanceDevPodVersion(&pkgprovider.ProInstance{Host: proInstance.Host}) combinedErrs = errors.Join(combinedErrs, err) proInstances = append(proInstances, DevPodProInstance{ Host: proInstance.Host, ProviderName: proInstance.Provider, Version: version, }) } return proInstances, combinedErrs } // (ThomasK33): Little type embedding here, so that we can // serialize the error strings when invoking json.Marshal. type PrintableError struct{ error } func (p PrintableError) MarshalJSON() ([]byte, error) { return json.Marshal(p.Error()) } ================================================ FILE: cmd/up.go ================================================ package cmd import ( "bytes" "context" "fmt" "io" "net" "os" "os/exec" "os/signal" "path/filepath" "slices" "strconv" "strings" "syscall" "github.com/blang/semver" "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/pkg/agent" "github.com/loft-sh/devpod/pkg/agent/tunnelserver" client2 "github.com/loft-sh/devpod/pkg/client" "github.com/loft-sh/devpod/pkg/client/clientimplementation" "github.com/loft-sh/devpod/pkg/command" "github.com/loft-sh/devpod/pkg/config" config2 "github.com/loft-sh/devpod/pkg/devcontainer/config" "github.com/loft-sh/devpod/pkg/devcontainer/sshtunnel" "github.com/loft-sh/devpod/pkg/ide" "github.com/loft-sh/devpod/pkg/ide/fleet" "github.com/loft-sh/devpod/pkg/ide/jetbrains" "github.com/loft-sh/devpod/pkg/ide/jupyter" "github.com/loft-sh/devpod/pkg/ide/openvscode" "github.com/loft-sh/devpod/pkg/ide/rstudio" "github.com/loft-sh/devpod/pkg/ide/vscode" "github.com/loft-sh/devpod/pkg/ide/zed" open2 "github.com/loft-sh/devpod/pkg/open" "github.com/loft-sh/devpod/pkg/platform" "github.com/loft-sh/devpod/pkg/port" provider2 "github.com/loft-sh/devpod/pkg/provider" devssh "github.com/loft-sh/devpod/pkg/ssh" "github.com/loft-sh/devpod/pkg/telemetry" "github.com/loft-sh/devpod/pkg/tunnel" "github.com/loft-sh/devpod/pkg/util" "github.com/loft-sh/devpod/pkg/version" workspace2 "github.com/loft-sh/devpod/pkg/workspace" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/skratchdot/open-golang/open" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" ) // UpCmd holds the up cmd flags type UpCmd struct { provider2.CLIOptions *flags.GlobalFlags Machine string ProviderOptions []string ConfigureSSH bool GPGAgentForwarding bool OpenIDE bool Reconfigure bool SSHConfigPath string DotfilesSource string DotfilesScript string DotfilesScriptEnv []string // Key=Value to pass to install script DotfilesScriptEnvFile []string // Paths to files containing Key=Value pairs to pass to install script } // NewUpCmd creates a new up command func NewUpCmd(f *flags.GlobalFlags) *cobra.Command { cmd := &UpCmd{ GlobalFlags: f, } upCmd := &cobra.Command{ Use: "up [flags] [workspace-path|workspace-name]", Short: "Starts a new workspace", RunE: func(cobraCmd *cobra.Command, args []string) error { devPodConfig, err := config.LoadConfig(cmd.Context, cmd.Provider) if err != nil { return err } if devPodConfig.ContextOption(config.ContextOptionSSHStrictHostKeyChecking) == "true" { cmd.StrictHostKeyChecking = true } ctx, cancel := WithSignals(cobraCmd.Context()) defer cancel() client, logger, err := cmd.prepareClient(ctx, devPodConfig, args) if err != nil { return fmt.Errorf("prepare workspace client: %w", err) } telemetry.CollectorCLI.SetClient(client) return cmd.Run(ctx, devPodConfig, client, args, logger) }, } upCmd.Flags().BoolVar(&cmd.ConfigureSSH, "configure-ssh", true, "If true will configure the ssh config to include the DevPod workspace") upCmd.Flags().BoolVar(&cmd.GPGAgentForwarding, "gpg-agent-forwarding", false, "If true forward the local gpg-agent to the DevPod workspace") upCmd.Flags().StringVar(&cmd.SSHConfigPath, "ssh-config", "", "The path to the ssh config to modify, if empty will use ~/.ssh/config") upCmd.Flags().StringVar(&cmd.DotfilesSource, "dotfiles", "", "The path or url to the dotfiles to use in the container") upCmd.Flags().StringVar(&cmd.DotfilesScript, "dotfiles-script", "", "The path in dotfiles directory to use to install the dotfiles, if empty will try to guess") upCmd.Flags().StringSliceVar(&cmd.DotfilesScriptEnv, "dotfiles-script-env", []string{}, "Extra environment variables to put into the dotfiles install script. E.g. MY_ENV_VAR=MY_VALUE") upCmd.Flags().StringSliceVar(&cmd.DotfilesScriptEnvFile, "dotfiles-script-env-file", []string{}, "The path to files containing environment variables to set for the dotfiles install script") upCmd.Flags().StringArrayVar(&cmd.IDEOptions, "ide-option", []string{}, "IDE option in the form KEY=VALUE") upCmd.Flags().StringVar(&cmd.DevContainerImage, "devcontainer-image", "", "The container image to use, this will override the devcontainer.json value in the project") upCmd.Flags().StringVar(&cmd.DevContainerPath, "devcontainer-path", "", "The path to the devcontainer.json relative to the project") upCmd.Flags().StringArrayVar(&cmd.ProviderOptions, "provider-option", []string{}, "Provider option in the form KEY=VALUE") upCmd.Flags().BoolVar(&cmd.Reconfigure, "reconfigure", false, "Reconfigure the options for this workspace. Only supported in DevPod Pro right now.") upCmd.Flags().BoolVar(&cmd.Recreate, "recreate", false, "If true will remove any existing containers and recreate them") upCmd.Flags().BoolVar(&cmd.Reset, "reset", false, "If true will remove any existing containers including sources, and recreate them") upCmd.Flags().StringSliceVar(&cmd.PrebuildRepositories, "prebuild-repository", []string{}, "Docker repository that hosts devpod prebuilds for this workspace") upCmd.Flags().StringArrayVar(&cmd.WorkspaceEnv, "workspace-env", []string{}, "Extra env variables to put into the workspace. E.g. MY_ENV_VAR=MY_VALUE") upCmd.Flags().StringSliceVar(&cmd.WorkspaceEnvFile, "workspace-env-file", []string{}, "The path to files containing a list of extra env variables to put into the workspace. E.g. MY_ENV_VAR=MY_VALUE") upCmd.Flags().StringArrayVar(&cmd.InitEnv, "init-env", []string{}, "Extra env variables to inject during the initialization of the workspace. E.g. MY_ENV_VAR=MY_VALUE") upCmd.Flags().StringVar(&cmd.ID, "id", "", "The id to use for the workspace") upCmd.Flags().StringVar(&cmd.Machine, "machine", "", "The machine to use for this workspace. The machine needs to exist beforehand or the command will fail. If the workspace already exists, this option has no effect") upCmd.Flags().StringVar(&cmd.IDE, "ide", "", "The IDE to open the workspace in. If empty will use vscode locally or in browser") upCmd.Flags().BoolVar(&cmd.OpenIDE, "open-ide", true, "If this is false and an IDE is configured, DevPod will only install the IDE server backend, but not open it") upCmd.Flags().Var(&cmd.GitCloneStrategy, "git-clone-strategy", "The git clone strategy DevPod uses to checkout git based workspaces. Can be full (default), blobless, treeless or shallow") upCmd.Flags().BoolVar(&cmd.GitCloneRecursiveSubmodules, "git-clone-recursive-submodules", false, "If true will clone git submodule repositories recursively") upCmd.Flags().StringVar(&cmd.GitSSHSigningKey, "git-ssh-signing-key", "", "The ssh key to use when signing git commits. Used to explicitly setup DevPod's ssh signature forwarding with given key. Should be same format as value of `git config user.signingkey`") upCmd.Flags().StringVar(&cmd.FallbackImage, "fallback-image", "", "The fallback image to use if no devcontainer configuration has been detected") upCmd.Flags().BoolVar(&cmd.DisableDaemon, "disable-daemon", false, "If enabled, will not install a daemon into the target machine to track activity") upCmd.Flags().StringVar(&cmd.Source, "source", "", "Optional source for the workspace. E.g. git:https://github.com/my-org/my-repo") // testing upCmd.Flags().StringVar(&cmd.DaemonInterval, "daemon-interval", "", "TESTING ONLY") _ = upCmd.Flags().MarkHidden("daemon-interval") upCmd.Flags().BoolVar(&cmd.ForceDockerless, "force-dockerless", false, "TESTING ONLY") _ = upCmd.Flags().MarkHidden("force-dockerless") return upCmd } // Run runs the command logic func (cmd *UpCmd) Run( ctx context.Context, devPodConfig *config.Config, client client2.BaseWorkspaceClient, args []string, log log.Logger, ) error { // a reset implies a recreate if cmd.Reset { cmd.Recreate = true } // check if we are a browser IDE and need to reuse the SSH_AUTH_SOCK targetIDE := client.WorkspaceConfig().IDE.Name // Check override if cmd.IDE != "" { targetIDE = cmd.IDE } if !cmd.Platform.Enabled && ide.ReusesAuthSock(targetIDE) { cmd.SSHAuthSockID = util.RandStringBytes(10) log.Debug("Reusing SSH_AUTH_SOCK", cmd.SSHAuthSockID) } else if cmd.Platform.Enabled && ide.ReusesAuthSock(targetIDE) { log.Debug("Reusing SSH_AUTH_SOCK is not supported with platform mode, consider launching the IDE from the platform UI") } // run devpod agent up result, err := cmd.devPodUp(ctx, devPodConfig, client, log) if err != nil { return err } else if result == nil { return fmt.Errorf("didn't receive a result back from agent") } else if cmd.Platform.Enabled { return nil } // get user from result user := config2.GetRemoteUser(result) var workdir string if result.MergedConfig != nil && result.MergedConfig.WorkspaceFolder != "" { workdir = result.MergedConfig.WorkspaceFolder } if client.WorkspaceConfig().Source.GitSubPath != "" { result.SubstitutionContext.ContainerWorkspaceFolder = filepath.Join(result.SubstitutionContext.ContainerWorkspaceFolder, client.WorkspaceConfig().Source.GitSubPath) workdir = result.SubstitutionContext.ContainerWorkspaceFolder } // configure container ssh if cmd.ConfigureSSH { devPodHome := "" envDevPodHome, ok := os.LookupEnv("DEVPOD_HOME") if ok { devPodHome = envDevPodHome } setupGPGAgentForwarding := cmd.GPGAgentForwarding || devPodConfig.ContextOption(config.ContextOptionGPGAgentForwarding) == "true" err = configureSSH(client, cmd.SSHConfigPath, user, workdir, setupGPGAgentForwarding, devPodHome) if err != nil { return err } log.Infof("Run 'ssh %s.devpod' to ssh into the devcontainer", client.Workspace()) } // setup git ssh signature if cmd.GitSSHSigningKey != "" { err = setupGitSSHSignature(cmd.GitSSHSigningKey, client, log) if err != nil { return err } } // setup dotfiles in the container err = setupDotfiles(cmd.DotfilesSource, cmd.DotfilesScript, cmd.DotfilesScriptEnvFile, cmd.DotfilesScriptEnv, client, devPodConfig, log) if err != nil { return err } // open ide if cmd.OpenIDE { ideConfig := client.WorkspaceConfig().IDE switch ideConfig.Name { case string(config.IDEVSCode): return vscode.Open( ctx, client.Workspace(), result.SubstitutionContext.ContainerWorkspaceFolder, vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true", vscode.FlavorStable, log, ) case string(config.IDEVSCodeInsiders): return vscode.Open( ctx, client.Workspace(), result.SubstitutionContext.ContainerWorkspaceFolder, vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true", vscode.FlavorInsiders, log, ) case string(config.IDECursor): return vscode.Open( ctx, client.Workspace(), result.SubstitutionContext.ContainerWorkspaceFolder, vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true", vscode.FlavorCursor, log, ) case string(config.IDECodium): return vscode.Open( ctx, client.Workspace(), result.SubstitutionContext.ContainerWorkspaceFolder, vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true", vscode.FlavorCodium, log, ) case string(config.IDEPositron): return vscode.Open( ctx, client.Workspace(), result.SubstitutionContext.ContainerWorkspaceFolder, vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true", vscode.FlavorPositron, log, ) case string(config.IDEWindsurf): return vscode.Open( ctx, client.Workspace(), result.SubstitutionContext.ContainerWorkspaceFolder, vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true", vscode.FlavorWindsurf, log, ) case string(config.IDEOpenVSCode): return startVSCodeInBrowser( cmd.GPGAgentForwarding, ctx, devPodConfig, client, result.SubstitutionContext.ContainerWorkspaceFolder, user, ideConfig.Options, cmd.SSHAuthSockID, log, ) case string(config.IDERustRover): return jetbrains.NewRustRoverServer(config2.GetRemoteUser(result), ideConfig.Options, log).OpenGateway(result.SubstitutionContext.ContainerWorkspaceFolder, client.Workspace()) case string(config.IDEGoland): return jetbrains.NewGolandServer(config2.GetRemoteUser(result), ideConfig.Options, log).OpenGateway(result.SubstitutionContext.ContainerWorkspaceFolder, client.Workspace()) case string(config.IDEPyCharm): return jetbrains.NewPyCharmServer(config2.GetRemoteUser(result), ideConfig.Options, log).OpenGateway(result.SubstitutionContext.ContainerWorkspaceFolder, client.Workspace()) case string(config.IDEPhpStorm): return jetbrains.NewPhpStorm(config2.GetRemoteUser(result), ideConfig.Options, log).OpenGateway(result.SubstitutionContext.ContainerWorkspaceFolder, client.Workspace()) case string(config.IDEIntellij): return jetbrains.NewIntellij(config2.GetRemoteUser(result), ideConfig.Options, log).OpenGateway(result.SubstitutionContext.ContainerWorkspaceFolder, client.Workspace()) case string(config.IDECLion): return jetbrains.NewCLionServer(config2.GetRemoteUser(result), ideConfig.Options, log).OpenGateway(result.SubstitutionContext.ContainerWorkspaceFolder, client.Workspace()) case string(config.IDERider): return jetbrains.NewRiderServer(config2.GetRemoteUser(result), ideConfig.Options, log).OpenGateway(result.SubstitutionContext.ContainerWorkspaceFolder, client.Workspace()) case string(config.IDERubyMine): return jetbrains.NewRubyMineServer(config2.GetRemoteUser(result), ideConfig.Options, log).OpenGateway(result.SubstitutionContext.ContainerWorkspaceFolder, client.Workspace()) case string(config.IDEWebStorm): return jetbrains.NewWebStormServer(config2.GetRemoteUser(result), ideConfig.Options, log).OpenGateway(result.SubstitutionContext.ContainerWorkspaceFolder, client.Workspace()) case string(config.IDEDataSpell): return jetbrains.NewDataSpellServer(config2.GetRemoteUser(result), ideConfig.Options, log).OpenGateway(result.SubstitutionContext.ContainerWorkspaceFolder, client.Workspace()) case string(config.IDEFleet): return startFleet(ctx, client, log) case string(config.IDEZed): return zed.Open(ctx, ideConfig.Options, config2.GetRemoteUser(result), result.SubstitutionContext.ContainerWorkspaceFolder, client.Workspace(), log) case string(config.IDEJupyterNotebook): return startJupyterNotebookInBrowser( cmd.GPGAgentForwarding, ctx, devPodConfig, client, user, ideConfig.Options, cmd.SSHAuthSockID, log, ) case string(config.IDERStudio): return startRStudioInBrowser( cmd.GPGAgentForwarding, ctx, devPodConfig, client, user, ideConfig.Options, cmd.SSHAuthSockID, log, ) } } return nil } func (cmd *UpCmd) devPodUp( ctx context.Context, devPodConfig *config.Config, client client2.BaseWorkspaceClient, log log.Logger, ) (*config2.Result, error) { var err error // only lock if we are not in platform mode if !cmd.Platform.Enabled { err := client.Lock(ctx) if err != nil { return nil, err } defer client.Unlock() } // get result var result *config2.Result switch client := client.(type) { case client2.WorkspaceClient: result, err = cmd.devPodUpMachine(ctx, devPodConfig, client, log) if err != nil { return nil, err } case client2.ProxyClient: result, err = cmd.devPodUpProxy(ctx, client, log) if err != nil { return nil, err } case client2.DaemonClient: result, err = cmd.devPodUpDaemon(ctx, client) if err != nil { return nil, err } default: return nil, fmt.Errorf("unsupported client type: %T", client) } // save result to file err = provider2.SaveWorkspaceResult(client.WorkspaceConfig(), result) if err != nil { return nil, fmt.Errorf("save workspace result: %w", err) } return result, nil } func (cmd *UpCmd) devPodUpProxy( ctx context.Context, client client2.ProxyClient, log log.Logger, ) (*config2.Result, error) { // create pipes stdoutReader, stdoutWriter, err := os.Pipe() if err != nil { return nil, err } stdinReader, stdinWriter, err := os.Pipe() if err != nil { return nil, err } defer stdoutWriter.Close() defer stdinWriter.Close() // start machine on stdio cancelCtx, cancel := context.WithCancel(ctx) defer cancel() // create up command errChan := make(chan error, 1) go func() { defer log.Debugf("Done executing up command") defer cancel() // build devpod up options workspace := client.WorkspaceConfig() baseOptions := cmd.CLIOptions baseOptions.ID = workspace.ID baseOptions.DevContainerPath = workspace.DevContainerPath baseOptions.DevContainerImage = workspace.DevContainerImage baseOptions.IDE = workspace.IDE.Name baseOptions.IDEOptions = nil baseOptions.Source = workspace.Source.String() for optionName, optionValue := range workspace.IDE.Options { baseOptions.IDEOptions = append( baseOptions.IDEOptions, optionName+"="+optionValue.Value, ) } // run devpod up elsewhere err = client.Up(ctx, client2.UpOptions{ CLIOptions: baseOptions, Debug: cmd.Debug, Stdin: stdinReader, Stdout: stdoutWriter, }) if err != nil { errChan <- fmt.Errorf("executing up proxy command: %w", err) } else { errChan <- nil } }() // create container etc. result, err := tunnelserver.RunUpServer( cancelCtx, stdoutReader, stdinWriter, true, true, client.WorkspaceConfig(), log, ) if err != nil { return nil, errors.Wrap(err, "run tunnel machine") } // wait until command finished return result, <-errChan } func (cmd *UpCmd) devPodUpDaemon( ctx context.Context, client client2.DaemonClient, ) (*config2.Result, error) { // build devpod up options workspace := client.WorkspaceConfig() baseOptions := cmd.CLIOptions baseOptions.ID = workspace.ID baseOptions.DevContainerPath = workspace.DevContainerPath baseOptions.DevContainerImage = workspace.DevContainerImage baseOptions.IDE = workspace.IDE.Name baseOptions.IDEOptions = nil baseOptions.Source = workspace.Source.String() for optionName, optionValue := range workspace.IDE.Options { baseOptions.IDEOptions = append( baseOptions.IDEOptions, optionName+"="+optionValue.Value, ) } // run devpod up elsewhere return client.Up(ctx, client2.UpOptions{ CLIOptions: baseOptions, Debug: cmd.Debug, }) } func (cmd *UpCmd) devPodUpMachine( ctx context.Context, devPodConfig *config.Config, client client2.WorkspaceClient, log log.Logger, ) (*config2.Result, error) { err := startWait(ctx, client, true, log) if err != nil { return nil, err } // compress info workspaceInfo, wInfo, err := client.AgentInfo(cmd.CLIOptions) if err != nil { return nil, err } // create container etc. log.Infof("Creating devcontainer...") defer log.Debugf("Done creating devcontainer") // if we run on a platform, we need to pass the platform options if cmd.Platform.Enabled { return buildAgentClient(ctx, client, cmd.CLIOptions, "up", log, tunnelserver.WithPlatformOptions(&cmd.Platform)) } // ssh tunnel command sshTunnelCmd := fmt.Sprintf("'%s' helper ssh-server --stdio", client.AgentPath()) if log.GetLevel() == logrus.DebugLevel { sshTunnelCmd += " --debug" } // create agent command agentCommand := fmt.Sprintf( "'%s' agent workspace up --workspace-info '%s'", client.AgentPath(), workspaceInfo, ) if log.GetLevel() == logrus.DebugLevel { agentCommand += " --debug" } agentInjectFunc := func(cancelCtx context.Context, sshCmd string, sshTunnelStdinReader, sshTunnelStdoutWriter *os.File, writer io.WriteCloser) error { return agent.InjectAgentAndExecute( cancelCtx, func(ctx context.Context, command string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { return client.Command(ctx, client2.CommandOptions{ Command: command, Stdin: stdin, Stdout: stdout, Stderr: stderr, }) }, client.AgentLocal(), client.AgentPath(), client.AgentURL(), true, sshCmd, sshTunnelStdinReader, sshTunnelStdoutWriter, writer, log.ErrorStreamOnly(), wInfo.InjectTimeout, ) } return sshtunnel.ExecuteCommand( ctx, client, devPodConfig.ContextOption(config.ContextOptionSSHAddPrivateKeys) == "true", agentInjectFunc, sshTunnelCmd, agentCommand, log, func(ctx context.Context, stdin io.WriteCloser, stdout io.Reader) (*config2.Result, error) { return tunnelserver.RunUpServer( ctx, stdout, stdin, client.AgentInjectGitCredentials(cmd.CLIOptions), client.AgentInjectDockerCredentials(cmd.CLIOptions), client.WorkspaceConfig(), log, ) }, ) } func startJupyterNotebookInBrowser( forwardGpg bool, ctx context.Context, devPodConfig *config.Config, client client2.BaseWorkspaceClient, user string, ideOptions map[string]config.OptionValue, authSockID string, logger log.Logger, ) error { if forwardGpg { err := performGpgForwarding(client, logger) if err != nil { return err } } // determine port jupyterAddress, jupyterPort, err := parseAddressAndPort( jupyter.Options.GetValue(ideOptions, jupyter.BindAddressOption), jupyter.DefaultServerPort, ) if err != nil { return err } // wait until reachable then open browser targetURL := fmt.Sprintf("http://localhost:%d/lab", jupyterPort) if jupyter.Options.GetValue(ideOptions, jupyter.OpenOption) == "true" { go func() { err = open2.Open(ctx, targetURL, logger) if err != nil { logger.Errorf("error opening jupyter notebook: %v", err) } logger.Infof( "Successfully started jupyter notebook in browser mode. Please keep this terminal open as long as you use Jupyter Notebook", ) }() } // start in browser logger.Infof("Starting jupyter notebook in browser mode at %s", targetURL) extraPorts := []string{fmt.Sprintf("%s:%d", jupyterAddress, jupyter.DefaultServerPort)} return startBrowserTunnel( ctx, devPodConfig, client, user, targetURL, false, extraPorts, authSockID, logger, ) } func startRStudioInBrowser( forwardGpg bool, ctx context.Context, devPodConfig *config.Config, client client2.BaseWorkspaceClient, user string, ideOptions map[string]config.OptionValue, authSockID string, logger log.Logger, ) error { if forwardGpg { err := performGpgForwarding(client, logger) if err != nil { return err } } // determine port addr, port, err := parseAddressAndPort( rstudio.Options.GetValue(ideOptions, rstudio.BindAddressOption), rstudio.DefaultServerPort, ) if err != nil { return err } // wait until reachable then open browser targetURL := fmt.Sprintf("http://localhost:%d", port) if rstudio.Options.GetValue(ideOptions, rstudio.OpenOption) == "true" { go func() { err = open2.Open(ctx, targetURL, logger) if err != nil { logger.Errorf("error opening rstudio: %v", err) } logger.Infof( "Successfully started RStudio Server in browser mode. Please keep this terminal open as long as you use it", ) }() } // start in browser logger.Infof("Starting RStudio server in browser mode at %s", targetURL) extraPorts := []string{fmt.Sprintf("%s:%d", addr, rstudio.DefaultServerPort)} return startBrowserTunnel( ctx, devPodConfig, client, user, targetURL, false, extraPorts, authSockID, logger, ) } func startFleet(ctx context.Context, client client2.BaseWorkspaceClient, logger log.Logger) error { // create ssh command stdout := &bytes.Buffer{} cmd, err := createSSHCommand( ctx, client, logger, []string{"--command", "cat " + fleet.FleetURLFile}, ) if err != nil { return err } cmd.Stdout = stdout err = cmd.Run() if err != nil { return command.WrapCommandError(stdout.Bytes(), err) } url := strings.TrimSpace(stdout.String()) if len(url) == 0 { return fmt.Errorf("seems like fleet is not running within the container") } logger.Warnf( "Fleet is exposed at a publicly reachable URL, please make sure to not disclose this URL to anyone as they will be able to reach your workspace from that", ) logger.Infof("Starting Fleet at %s ...", url) err = open.Run(url) if err != nil { return err } return nil } func startVSCodeInBrowser( forwardGpg bool, ctx context.Context, devPodConfig *config.Config, client client2.BaseWorkspaceClient, workspaceFolder, user string, ideOptions map[string]config.OptionValue, authSockID string, logger log.Logger, ) error { if forwardGpg { err := performGpgForwarding(client, logger) if err != nil { return err } } // determine port vscodeAddress, vscodePort, err := parseAddressAndPort( openvscode.Options.GetValue(ideOptions, openvscode.BindAddressOption), openvscode.DefaultVSCodePort, ) if err != nil { return err } // wait until reachable then open browser targetURL := fmt.Sprintf("http://localhost:%d/?folder=%s", vscodePort, workspaceFolder) if openvscode.Options.GetValue(ideOptions, openvscode.OpenOption) == "true" { go func() { err = open2.Open(ctx, targetURL, logger) if err != nil { logger.Errorf("error opening vscode: %v", err) } logger.Infof( "Successfully started vscode in browser mode. Please keep this terminal open as long as you use VSCode browser version", ) }() } // start in browser logger.Infof("Starting vscode in browser mode at %s", targetURL) forwardPorts := openvscode.Options.GetValue(ideOptions, openvscode.ForwardPortsOption) == "true" extraPorts := []string{fmt.Sprintf("%s:%d", vscodeAddress, openvscode.DefaultVSCodePort)} return startBrowserTunnel( ctx, devPodConfig, client, user, targetURL, forwardPorts, extraPorts, authSockID, logger, ) } func parseAddressAndPort(bindAddressOption string, defaultPort int) (string, int, error) { var ( err error address string portName int ) if bindAddressOption == "" { portName, err = port.FindAvailablePort(defaultPort) if err != nil { return "", 0, err } address = fmt.Sprintf("%d", portName) } else { address = bindAddressOption _, port, err := net.SplitHostPort(address) if err != nil { return "", 0, fmt.Errorf("parse host:port: %w", err) } else if port == "" { return "", 0, fmt.Errorf("parse ADDRESS: expected host:port, got %s", address) } portName, err = strconv.Atoi(port) if err != nil { return "", 0, fmt.Errorf("parse host:port: %w", err) } } return address, portName, nil } // setupBackhaul sets up a long running command in the container to ensure an SSH connection is kept alive func setupBackhaul(client client2.BaseWorkspaceClient, authSockId string, log log.Logger) error { execPath, err := os.Executable() if err != nil { return err } remoteUser, err := devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath) if err != nil { remoteUser = "root" } dotCmd := exec.Command( execPath, "ssh", "--agent-forwarding=true", fmt.Sprintf("--reuse-ssh-auth-sock=%s", authSockId), "--start-services=false", "--user", remoteUser, "--context", client.Context(), client.Workspace(), "--log-output=raw", "--command", "while true; do sleep 6000000; done", // sleep infinity is not available on all systems ) if log.GetLevel() == logrus.DebugLevel { dotCmd.Args = append(dotCmd.Args, "--debug") } log.Info("Setting up backhaul SSH connection") writer := log.Writer(logrus.InfoLevel, false) dotCmd.Stdout = writer dotCmd.Stderr = writer err = dotCmd.Run() if err != nil { return err } log.Infof("Done setting up backhaul") return nil } func startBrowserTunnel( ctx context.Context, devPodConfig *config.Config, client client2.BaseWorkspaceClient, user, targetURL string, forwardPorts bool, extraPorts []string, authSockID string, logger log.Logger, ) error { // Setup a backhaul SSH connection using the remote user so there is an AUTH SOCK to use // With normal IDEs this would be the SSH connection made by the IDE // authSockID is not set when in proxy mode since we cannot use the proxies ssh-agent if authSockID != "" { go func() { if err := setupBackhaul(client, authSockID, logger); err != nil { logger.Error("Failed to setup backhaul SSH connection: ", err) } }() } // handle this directly with the daemon client daemonClient, ok := client.(client2.DaemonClient) if ok { toolClient, _, err := daemonClient.SSHClients(ctx, user) if err != nil { return err } defer toolClient.Close() err = startServicesDaemon(ctx, devPodConfig, daemonClient, toolClient, user, logger, forwardPorts, extraPorts, ) if err != nil { return err } <-ctx.Done() return nil } err := tunnel.NewTunnel( ctx, func(ctx context.Context, stdin io.Reader, stdout io.Writer) error { writer := logger.Writer(logrus.DebugLevel, false) defer writer.Close() cmd, err := createSSHCommand(ctx, client, logger, []string{ "--log-output=raw", fmt.Sprintf("--reuse-ssh-auth-sock=%s", authSockID), "--stdio", }) if err != nil { return err } cmd.Stdout = stdout cmd.Stdin = stdin cmd.Stderr = writer return cmd.Run() }, func(ctx context.Context, containerClient *ssh.Client) error { // print port to console streamLogger, ok := logger.(*log.StreamLogger) if ok { streamLogger.JSON(logrus.InfoLevel, map[string]string{ "url": targetURL, "done": "true", }) } configureDockerCredentials := devPodConfig.ContextOption(config.ContextOptionSSHInjectDockerCredentials) == "true" configureGitCredentials := devPodConfig.ContextOption(config.ContextOptionSSHInjectGitCredentials) == "true" configureGitSSHSignatureHelper := devPodConfig.ContextOption(config.ContextOptionGitSSHSignatureForwarding) == "true" // run in container err := tunnel.RunServices( ctx, devPodConfig, containerClient, user, forwardPorts, extraPorts, nil, client.WorkspaceConfig(), configureDockerCredentials, configureGitCredentials, configureGitSSHSignatureHelper, logger, ) if err != nil { return fmt.Errorf("run credentials server in browser tunnel: %w", err) } <-ctx.Done() return nil }, ) if err != nil { return err } return nil } func configureSSH(client client2.BaseWorkspaceClient, sshConfigPath, user, workdir string, gpgagent bool, devPodHome string) error { path, err := devssh.ResolveSSHConfigPath(sshConfigPath) if err != nil { return errors.Wrap(err, "Invalid ssh config path") } sshConfigPath = path err = devssh.ConfigureSSHConfig( sshConfigPath, client.Context(), client.Workspace(), user, workdir, gpgagent, devPodHome, log.Default, ) if err != nil { return err } return nil } func mergeDevPodUpOptions(baseOptions *provider2.CLIOptions) error { oldOptions := *baseOptions found, err := clientimplementation.DecodeOptionsFromEnv( clientimplementation.DevPodFlagsUp, baseOptions, ) if err != nil { return fmt.Errorf("decode up options: %w", err) } else if found { baseOptions.WorkspaceEnv = append(oldOptions.WorkspaceEnv, baseOptions.WorkspaceEnv...) baseOptions.InitEnv = append(oldOptions.InitEnv, baseOptions.InitEnv...) baseOptions.PrebuildRepositories = append(oldOptions.PrebuildRepositories, baseOptions.PrebuildRepositories...) baseOptions.IDEOptions = append(oldOptions.IDEOptions, baseOptions.IDEOptions...) } err = clientimplementation.DecodePlatformOptionsFromEnv(&baseOptions.Platform) if err != nil { return fmt.Errorf("decode platform options: %w", err) } return nil } func mergeEnvFromFiles(baseOptions *provider2.CLIOptions) error { var variables []string for _, file := range baseOptions.WorkspaceEnvFile { envFromFile, err := config2.ParseKeyValueFile(file) if err != nil { return err } variables = append(variables, envFromFile...) } baseOptions.WorkspaceEnv = append(baseOptions.WorkspaceEnv, variables...) return nil } func createSSHCommand( ctx context.Context, client client2.BaseWorkspaceClient, logger log.Logger, extraArgs []string, ) (*exec.Cmd, error) { execPath, err := os.Executable() if err != nil { return nil, err } args := []string{ "ssh", "--user=root", "--agent-forwarding=false", "--start-services=false", "--context", client.Context(), client.Workspace(), } if logger.GetLevel() == logrus.DebugLevel { args = append(args, "--debug") } args = append(args, extraArgs...) return exec.CommandContext(ctx, execPath, args...), nil } func setupDotfiles( dotfiles, script string, envFiles, envKeyValuePairs []string, client client2.BaseWorkspaceClient, devPodConfig *config.Config, log log.Logger, ) error { dotfilesRepo := devPodConfig.ContextOption(config.ContextOptionDotfilesURL) if dotfiles != "" { dotfilesRepo = dotfiles } dotfilesScript := devPodConfig.ContextOption(config.ContextOptionDotfilesScript) if script != "" { dotfilesScript = script } if dotfilesRepo == "" { log.Debug("No dotfiles repo specified, skipping") return nil } log.Infof("Dotfiles git repository %s specified", dotfilesRepo) log.Debug("Cloning dotfiles into the devcontainer...") dotCmd, err := buildDotCmd(devPodConfig, dotfilesRepo, dotfilesScript, envFiles, envKeyValuePairs, client, log) if err != nil { return err } if log.GetLevel() == logrus.DebugLevel { dotCmd.Args = append(dotCmd.Args, "--debug") } log.Debugf("Running dotfiles setup command: %v", dotCmd.Args) writer := log.Writer(logrus.InfoLevel, false) dotCmd.Stdout = writer dotCmd.Stderr = writer err = dotCmd.Run() if err != nil { return err } log.Infof("Done setting up dotfiles into the devcontainer") return nil } func buildDotCmdAgentArguments(devPodConfig *config.Config, dotfilesRepo, dotfilesScript string, log log.Logger) []string { agentArguments := []string{ "agent", "workspace", "install-dotfiles", "--repository", dotfilesRepo, } if devPodConfig.ContextOption(config.ContextOptionSSHStrictHostKeyChecking) == "true" { agentArguments = append(agentArguments, "--strict-host-key-checking") } if log.GetLevel() == logrus.DebugLevel { agentArguments = append(agentArguments, "--debug") } if dotfilesScript != "" { log.Infof("Dotfiles script %s specified", dotfilesScript) agentArguments = append(agentArguments, "--install-script", dotfilesScript) } return agentArguments } func buildDotCmd(devPodConfig *config.Config, dotfilesRepo, dotfilesScript string, envFiles, envKeyValuePairs []string, client client2.BaseWorkspaceClient, log log.Logger) (*exec.Cmd, error) { sshCmd := []string{ "ssh", "--agent-forwarding=true", "--start-services=true", } envFilesKeyValuePairs, err := collectDotfilesScriptEnvKeyvaluePairs(envFiles) if err != nil { return nil, err } // Collect file-based and CLI options env variables names (aka keys) and // configure ssh env var passthrough with send-env allEnvKeyValuesPairs := slices.Concat(envFilesKeyValuePairs, envKeyValuePairs) allEnvKeys := extractKeysFromEnvKeyValuePairs(allEnvKeyValuesPairs) for _, envKey := range allEnvKeys { sshCmd = append(sshCmd, "--send-env", envKey) } remoteUser, err := devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath) if err != nil { remoteUser = "root" } agentArguments := buildDotCmdAgentArguments(devPodConfig, dotfilesRepo, dotfilesScript, log) sshCmd = append(sshCmd, "--user", remoteUser, "--context", client.Context(), client.Workspace(), "--log-output=raw", "--command", agent.ContainerDevPodHelperLocation+" "+strings.Join(agentArguments, " "), ) execPath, err := os.Executable() if err != nil { return nil, err } dotCmd := exec.Command( execPath, sshCmd..., ) dotCmd.Env = append(dotCmd.Environ(), allEnvKeyValuesPairs...) return dotCmd, nil } func extractKeysFromEnvKeyValuePairs(envKeyValuePairs []string) []string { keys := []string{} for _, env := range envKeyValuePairs { keyValue := strings.SplitN(env, "=", 2) if len(keyValue) == 2 { keys = append(keys, keyValue[0]) } } return keys } func collectDotfilesScriptEnvKeyvaluePairs(envFiles []string) ([]string, error) { keyValues := []string{} for _, file := range envFiles { envFromFile, err := config2.ParseKeyValueFile(file) if err != nil { return nil, err } keyValues = append(keyValues, envFromFile...) } return keyValues, nil } func setupGitSSHSignature(signingKey string, client client2.BaseWorkspaceClient, log log.Logger) error { execPath, err := os.Executable() if err != nil { return err } remoteUser, err := devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath) if err != nil { remoteUser = "root" } err = exec.Command( execPath, "ssh", "--agent-forwarding=true", "--start-services=true", "--user", remoteUser, "--context", client.Context(), client.Workspace(), "--command", fmt.Sprintf("devpod agent git-ssh-signature-helper %s", signingKey), ).Run() if err != nil { log.Error("failure in setting up git ssh signature helper") } return nil } func performGpgForwarding( client client2.BaseWorkspaceClient, log log.Logger, ) error { log.Debug("gpg forwarding enabled, performing immediately") execPath, err := os.Executable() if err != nil { return err } remoteUser, err := devssh.GetUser(client.WorkspaceConfig().ID, client.WorkspaceConfig().SSHConfigPath) if err != nil { remoteUser = "root" } log.Info("forwarding gpg-agent") // perform in background an ssh command forwarding the // gpg agent, in order to have it immediately take effect go func() { err = exec.Command( execPath, "ssh", "--gpg-agent-forwarding=true", "--agent-forwarding=true", "--start-services=true", "--user", remoteUser, "--context", client.Context(), client.Workspace(), "--log-output=raw", "--command", "sleep infinity", ).Run() if err != nil { log.Error("failure in forwarding gpg-agent") } }() return nil } // checkProviderUpdate currently only ensures the local provider is in sync with the remote for DevPod Pro instances // Potentially auto-upgrade other providers in the future. func checkProviderUpdate(devPodConfig *config.Config, proInstance *provider2.ProInstance, log log.Logger) error { if version.GetVersion() == version.DevVersion { log.Debugf("Skipping provider upgrade check during development") return nil } if proInstance == nil { log.Debugf("No pro instance available, skipping provider upgrade check") return nil } // compare versions newVersion, err := platform.GetProInstanceDevPodVersion(proInstance) if err != nil { return fmt.Errorf("version for pro instance %s: %w", proInstance.Host, err) } p, err := workspace2.FindProvider(devPodConfig, proInstance.Provider, log) if err != nil { return fmt.Errorf("get provider config for pro provider %s: %w", proInstance.Provider, err) } if p.Config.Version == version.DevVersion { return nil } if p.Config.Source.Internal { return nil } v1, err := semver.Parse(strings.TrimPrefix(newVersion, "v")) if err != nil { return fmt.Errorf("parse version %s: %w", newVersion, err) } v2, err := semver.Parse(strings.TrimPrefix(p.Config.Version, "v")) if err != nil { return fmt.Errorf("parse version %s: %w", p.Config.Version, err) } if v1.Compare(v2) == 0 { return nil } log.Infof("New provider version available, attempting to update %s from %s to %s", proInstance.Provider, p.Config.Version, newVersion) providerSource, err := workspace2.ResolveProviderSource(devPodConfig, proInstance.Provider, log) if err != nil { return fmt.Errorf("resolve provider source %s: %w", proInstance.Provider, err) } splitted := strings.Split(providerSource, "@") if len(splitted) == 0 { return fmt.Errorf("no provider source found %s", providerSource) } providerSource = splitted[0] + "@" + newVersion _, err = workspace2.UpdateProvider(devPodConfig, proInstance.Provider, providerSource, log) if err != nil { return fmt.Errorf("update provider %s: %w", proInstance.Provider, err) } log.Donef("Successfully updated provider %s", proInstance.Provider) return nil } func getProInstance(devPodConfig *config.Config, providerName string, log log.Logger) *provider2.ProInstance { proInstances, err := workspace2.ListProInstances(devPodConfig, log) if err != nil { return nil } else if len(proInstances) == 0 { return nil } proInstance, ok := workspace2.FindProviderProInstance(proInstances, providerName) if !ok { return nil } return proInstance } func (cmd *UpCmd) prepareClient(ctx context.Context, devPodConfig *config.Config, args []string) (client2.BaseWorkspaceClient, log.Logger, error) { // try to parse flags from env if err := mergeDevPodUpOptions(&cmd.CLIOptions); err != nil { return nil, nil, err } var logger log.Logger = log.Default if cmd.Platform.Enabled { logger = logger.ErrorStreamOnly() logger.Debug("Running in platform mode") logger.Debug("Using error output stream") // merge context options from env config.MergeContextOptions(devPodConfig.Current(), os.Environ()) } if err := mergeEnvFromFiles(&cmd.CLIOptions); err != nil { return nil, logger, err } var source *provider2.WorkspaceSource if cmd.Source != "" { source = provider2.ParseWorkspaceSource(cmd.Source) if source == nil { return nil, nil, fmt.Errorf("workspace source is missing") } else if source.LocalFolder != "" && cmd.Platform.Enabled { return nil, nil, fmt.Errorf("local folder is not supported in platform mode. Please specify a git repository instead") } } if cmd.SSHConfigPath == "" { cmd.SSHConfigPath = devPodConfig.ContextOption(config.ContextOptionSSHConfigPath) } client, err := workspace2.Resolve( ctx, devPodConfig, cmd.IDE, cmd.IDEOptions, args, cmd.ID, cmd.Machine, cmd.ProviderOptions, cmd.Reconfigure, cmd.DevContainerImage, cmd.DevContainerPath, cmd.SSHConfigPath, source, cmd.UID, true, cmd.Owner, logger, ) if err != nil { return nil, logger, err } if !cmd.Platform.Enabled { proInstance := getProInstance(devPodConfig, client.Provider(), logger) err = checkProviderUpdate(devPodConfig, proInstance, logger) if err != nil { return nil, logger, err } } return client, logger, nil } func WithSignals(ctx context.Context) (context.Context, func()) { ctx, cancel := context.WithCancel(ctx) signals := make(chan os.Signal, 1) signal.Notify(signals, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGQUIT) go func() { select { case <-signals: cancel() case <-ctx.Done(): } }() go func() { <-ctx.Done() <-signals // force shutdown if context is done and we receive another signal os.Exit(1) }() return ctx, func() { cancel() signal.Stop(signals) } } ================================================ FILE: cmd/upgrade.go ================================================ package cmd import ( "github.com/loft-sh/devpod/pkg/upgrade" "github.com/loft-sh/log" "github.com/pkg/errors" "github.com/spf13/cobra" ) // UpgradeCmd is a struct that defines a command call for "upgrade" type UpgradeCmd struct { log log.Logger Version string } // NewUpgradeCmd creates a new upgrade command func NewUpgradeCmd() *cobra.Command { cmd := &UpgradeCmd{log: log.GetInstance()} upgradeCmd := &cobra.Command{ Use: "upgrade", Short: "Upgrade the DevPod CLI to the newest version", Args: cobra.NoArgs, RunE: cmd.Run, } upgradeCmd.Flags().StringVar(&cmd.Version, "version", "", "The version to update to. Defaults to the latest stable version available") return upgradeCmd } // Run executes the command logic func (cmd *UpgradeCmd) Run(*cobra.Command, []string) error { err := upgrade.Upgrade(cmd.Version, cmd.log) if err != nil { return errors.Errorf("unable to upgrade: %v", err) } return nil } ================================================ FILE: cmd/use/use.go ================================================ package use import ( "github.com/loft-sh/devpod/cmd/flags" "github.com/loft-sh/devpod/cmd/ide" "github.com/loft-sh/devpod/cmd/provider" "github.com/spf13/cobra" ) // NewUseCmd returns a new root command func NewUseCmd(flags *flags.GlobalFlags) *cobra.Command { useCmd := &cobra.Command{ Use: "use", Short: "Use DevPod resources", } // use provider useProviderCmd := provider.NewUseCmd(flags) useProviderCmd.Use = "provider" useCmd.AddCommand(useProviderCmd) // use ide useIDECmd := ide.NewUseCmd(flags) useIDECmd.Use = "ide" useCmd.AddCommand(useIDECmd) return useCmd } ================================================ FILE: cmd/version.go ================================================ package cmd import ( "fmt" "github.com/loft-sh/devpod/pkg/version" "github.com/spf13/cobra" ) // VersionCmd holds the ws-tunnel cmd flags type VersionCmd struct { } // NewVersionCmd creates a new ws-tunnel command func NewVersionCmd() *cobra.Command { cmd := &VersionCmd{} versionCmd := &cobra.Command{ Use: "version", Short: "Prints the version", Args: cobra.NoArgs, RunE: cmd.Run, } return versionCmd } // Run runs the command logic func (cmd *VersionCmd) Run(_ *cobra.Command, _ []string) error { fmt.Print(version.GetVersion()) return nil } ================================================ FILE: community.yaml ================================================ providers: - repository: https://github.com/alexandrevilain/devpod-provider-ovhcloud - repository: https://github.com/dirien/devpod-provider-exoscale - repository: https://github.com/dirien/devpod-provider-scaleway - repository: https://github.com/mrsimonemms/devpod-provider-hetzner - repository: https://github.com/cloudbit-ch/devpod-provider-cloudbit - repository: https://github.com/flowswiss/devpod-provider-flow - repository: https://github.com/navaneeth-dev/devpod-provider-vultr - repository: https://github.com/minhio/devpod-provider-multipass - repository: https://github.com/akyriako/devpod-provider-opentelekomcloud - repository: https://github.com/stackitcloud/devpod-provider-stackit ================================================ FILE: desktop/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? src-tauri/bin/* .DS_Store ================================================ FILE: desktop/.npmrc ================================================ save-exact=true ================================================ FILE: desktop/.prettierignore ================================================ /src-tauri /public /.vscode /dist ================================================ FILE: desktop/.prettierrc ================================================ { "trailingComma": "es5", "printWidth": 100, "tabWidth": 2, "semi": false, "singleQuote": false, "bracketSpacing": true, "bracketSameLine": true } ================================================ FILE: desktop/.vscode/extensions.json ================================================ { "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] } ================================================ FILE: desktop/README.md ================================================ # Devpod Desktop [Open Example Devpod](devpod://open?workspace=vscode-remote-try-go&source=https://github.com/Microsoft/vscode-remote-try-go&provider=docker) ## Development 1. Install [NodeJS](https://nodejs.org/en/) 2. Install [Yarn](https://yarnpkg.com/getting-started/install) and make sure you use yarn v1, by running `yarn set version 1.22.1` 3. Install [Rust](https://www.rust-lang.org/tools/install) 4. Install [Go](https://go.dev/doc/install) 5. Run `./hack/rebuild.sh` from the root directory of the repo 6. Install dependencies with `yarn` in the `desktop` directory 7. Run `yarn tauri dev` in the `desktop` directory ### Build dependencies To build the app on Linux, you will need the following dependencies: ```bash sudo apt-get install libappindicator3-1 libgdk-pixbuf2.0-0 libbsd0 libxdmcp6 \ libwmf-0.2-7 libwmf-0.2-7-gtk libgtk-3-0 libwmf-dev libwebkit2gtk-4.0-37 \ librust-openssl-sys-dev librust-glib-sys-dev sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev \ libayatana-appindicator3-dev librsvg2-dev file build-essential ``` ### Additional Information Make sure all of your dependencies are installed and up to date by running `yarn` and `cd src-tauri && cargo update`. Frontend code lives in `src` Backend code lives in `src-tauri` Entrypoint for the application is the `main` function in `src-tauri/main.rs`. It instructs tauri to set up the application, bootstrap the webview and serve our static assets. As of now, we just bundle all of the javascript into one file, so we don't have any prerendering or code splitting going on. To spin up the application in development mode, run `yarn tauri dev`. It will report both the frontend webserver output (vite) and the backend logs to your current terminal. Tauri should automatically restart the app if your backend code changes and vite is responsible for hot module updates in the frontend. Enable debug logging to stdout during development with `DEBUG=true yarn tauri dev`. If you just want to preview the project locally, make sure to disabled the auto update feature by setting `desktop/src-tauri/tauri.conf.json->updater.active=false`. Please be careful not to commit this change later on. Once you're happy with the current state, give it a spin in release mode by running `yarn tauri build`. You can find the packaged version of the application in the `src-tauri/target/release/{PLATFORM}` folder. ## Check Type Errors Run `yarn types:check` to check for errors ## Versioning The apps version is determined by the one in `package.json`. Be careful not to add one in `tauri.conf.json` as it override the current one. You can upgrade the version manually or run `yarn version ...` ## Build desktop app If your development environment is setup successfully and you're able to run `yarn desktop:dev` without problems, you also should be able to build the app locally by runnning `yarn desktop:build:dev`. Notice the `:dev` suffix, if you omit this it'll try to build with the updater enabled. This won't work on your machine as it requires sensitive information. The output of the command can be found in `desktop/src-tauri/target/release/bundle`. ================================================ FILE: desktop/eslint.config.js ================================================ import { fixupConfigRules, fixupPluginRules } from "@eslint/compat" import react from "eslint-plugin-react" import typescriptEslint from "@typescript-eslint/eslint-plugin" import tanstackQuery from "@tanstack/eslint-plugin-query" import globals from "globals" import tsParser from "@typescript-eslint/parser" import path from "node:path" import { fileURLToPath } from "node:url" import js from "@eslint/js" import { FlatCompat } from "@eslint/eslintrc" import reactRefresh from "eslint-plugin-react-refresh" const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const compat = new FlatCompat({ baseDirectory: __dirname, recommendedConfig: js.configs.recommended, allConfig: js.configs.all, }) export default [ { ignores: ["dist/**/*", "src-tauri/**/*", "public/**/*", "src/gen/**/*"], }, ...fixupConfigRules( compat.extends( "eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:react-hooks/recommended", "prettier", "plugin:@tanstack/eslint-plugin-query/recommended" ) ), reactRefresh.configs.recommended, { plugins: { react: fixupPluginRules(react), "@typescript-eslint": fixupPluginRules(typescriptEslint), "@tanstack/query": fixupPluginRules(tanstackQuery), }, languageOptions: { globals: { ...globals.browser, ...globals.node, Atomics: "readonly", SharedArrayBuffer: "readonly", }, parser: tsParser, ecmaVersion: 5, parserOptions: { project: true, tsConfigRootDir: __dirname, }, }, settings: { react: { version: "detect", }, }, rules: { "react/react-in-jsx-scope": "off", "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": ["error"], "padding-line-between-statements": [ "warn", { blankLine: "always", prev: "*", next: "return", }, ], "no-warning-comments": [ "error", { terms: ["fixme"], location: "start", }, ], "@typescript-eslint/no-unnecessary-condition": [ "warn", { allowConstantLoopConditions: true, }, ], "@typescript-eslint/naming-convention": [ "error", { selector: ["typeParameter", "typeAlias"], format: ["PascalCase"], prefix: ["T"], }, { selector: ["interface"], format: ["PascalCase"], prefix: ["I"], }, { selector: ["enum"], format: ["PascalCase"], prefix: ["E"], }, ], }, }, ] ================================================ FILE: desktop/flatpak/DevPod.metainfo.xml ================================================ sh.loft.devpod Devpod Codespaces but open-source, client-only and unopinionated: Works with any IDE and lets you use any cloud, kubernetes or just localhost docker. Loft Labs FSFAP MPL-2.0 https://devpod.sh https://github.com/loft-sh/devpod https://github.com/loft-sh/devpod/issues pointing keyboard touch

Codespaces but open-source, client-only and unopinionated: Works with any IDE and lets you use any cloud, kubernetes or just localhost docker.

sh.loft.devpod.desktop https://devpod.sh/docs/media/devpod-architecture-2.png Devpod Architecture https://devpod.sh/docs/media/add-provider-2.png Adding a Provider https://devpod.sh/docs/media/create-workspace-vscode-browser.png Using vscode in the browser

Release notes can be found at https://github.com/loft-sh/devpod/releases/tag/v0.6.10

================================================ FILE: desktop/flatpak/sh.loft.devpod.tmpl ================================================ id: sh.loft.devpod runtime: org.gnome.Platform runtime-version: '47' sdk: org.gnome.Sdk command: "sh.loft.devpod" finish-args: - --socket=wayland # Permission needed to show the window - --socket=fallback-x11 # Permission needed to show the window - --device=dri # OpenGL, not necessary for all projects - --share=ipc - --share=network - --socket=ssh-auth - --socket=gpg-agent - --filesystem=home - --talk-name=org.freedesktop.Flatpak - --talk-name=org.freedesktop.Notifications - --talk-name=org.kde.StatusNotifierWatcher - --filesystem=xdg-run/keyring modules: - shared-modules/libayatana-appindicator/libayatana-appindicator-gtk3.json - name: devpod buildsystem: simple sources: - type: file url: https://github.com/loft-sh/devpod/releases/download/v${VERSION}/DevPod_${VERSION}_amd64.deb sha256: ${SHA256} only-arches: [x86_64] - type: file url: https://github.com/loft-sh/devpod/releases/download/v${VERSION}/DevPod.desktop sha256: ${DESKTOP_SHA256} - type: file url: https://github.com/loft-sh/devpod/releases/download/v${VERSION}/DevPod.metainfo.xml sha256: ${META_SHA256} - type: file path: devpod-wrapper build-commands: - ar -x *.deb - tar -xf data.tar.gz - cp devpod-wrapper /app/bin/devpod-cli - chmod +x /app/bin/devpod-cli - install -Dm755 usr/bin/devpod-cli /app/bin/devpod-bin - install -Dm755 usr/bin/DevPod\ Desktop /app/bin/sh.loft.devpod - install -Dm644 DevPod.desktop /app/share/applications/sh.loft.devpod.desktop - desktop-file-edit --set-key Exec --set-value sh.loft.devpod /app/share/applications/sh.loft.devpod.desktop - desktop-file-edit --set-icon sh.loft.devpod /app/share/applications/sh.loft.devpod.desktop - install -Dm644 usr/share/icons/hicolor/128x128/apps/DevPod\ Desktop.png /app/share/icons/hicolor/128x128/apps/sh.loft.devpod.png - install -Dm644 usr/share/icons/hicolor/32x32/apps/DevPod\ Desktop.png /app/share/icons/hicolor/32x32/apps/sh.loft.devpod.png - install -Dm644 usr/share/icons/hicolor/256x256@2/apps/DevPod\ Desktop.png /app/share/icons/hicolor/256x256@2/apps/sh.loft.devpod.png - install -Dm644 DevPod.metainfo.xml /app/share/metainfo/sh.loft.devpod.metainfo.xml ================================================ FILE: desktop/index.html ================================================ DevPod
================================================ FILE: desktop/package.json ================================================ { "name": "devpod", "private": true, "version": "0.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", "tauri": "tauri", "desktop:dev": "tauri dev --config src-tauri/tauri-dev.conf.json", "desktop:dev:debug": "DEBUG=true yarn desktop:dev", "desktop:build:dev": "DEBUG=true tauri build --config src-tauri/tauri-dev.conf.json", "desktop:build:flatpak": "tauri build --config src-tauri/tauri-flatpak.conf.json", "desktop:build:flatpak:debug": "DEBUG=true tauri build --config src-tauri/tauri-flatpak.conf.json --debug", "desktop:build:debug": "tauri build --debug", "desktop:build": "tauri build", "format:check": "prettier --check .", "format:fix": "prettier --write .", "types:check": "tsc -p ./tsconfig.json --noEmit", "lint:ci": "eslint . --ext '.ts,.tsx'" }, "dependencies": { "@chakra-ui/icons": "2.1.1", "@chakra-ui/react": "2.8.1", "@emotion/react": "11.11.1", "@emotion/styled": "11.11.0", "@headlessui/react": "1.7.17", "@kubernetes/client-node": "1.0.0", "@loft-enterprise/client": "https://github.com/loft-sh/loft-javascript-client#v4.3.0-devpod.alpha.11", "@tanstack/react-query": "4.36.1", "@tanstack/react-query-devtools": "4.36.1", "@tanstack/react-table": "8.10.7", "@tauri-apps/api": "^2.4.0", "@tauri-apps/plugin-clipboard-manager": "2.2.2", "@tauri-apps/plugin-dialog": "2.2.0", "@tauri-apps/plugin-fs": "2.2.0", "@tauri-apps/plugin-log": "2.3.1", "@tauri-apps/plugin-os": "2.2.1", "@tauri-apps/plugin-process": "2.2.0", "@tauri-apps/plugin-shell": "2.2.0", "@tauri-apps/plugin-store": "2.2.0", "@tauri-apps/plugin-updater": "2.6.1", "@types/lodash.debounce": "4.0.9", "@xterm/addon-fit": "0.10.0", "@xterm/xterm": "5.5.0", "compare-versions": "6.1.1", "dayjs": "1.11.10", "framer-motion": "10.16.9", "js-yaml": "4.1.0", "jszip": "3.10.1", "lodash.debounce": "4.0.8", "markdown-to-jsx": "7.3.2", "react": "18.2.0", "react-dom": "18.2.0", "react-error-boundary": "4.1.2", "react-hook-form": "7.48.2", "react-icons": "4.12.0", "react-router": "6.20.0", "react-router-dom": "6.20.0", "tauri-plugin-store-api": "https://github.com/tauri-apps/tauri-plugin-store#v1", "uuid": "9.0.1" }, "devDependencies": { "@eslint/compat": "1.2.7", "@eslint/eslintrc": "3.3.0", "@eslint/js": "9.22.0", "@tanstack/eslint-plugin-query": "4.36.1", "@tauri-apps/cli": "2.4.0", "@types/node": "18.15.3", "@types/react": "18.2.28", "@types/react-dom": "18.2.13", "@types/uuid": "9.0.5", "@typescript-eslint/eslint-plugin": "8.26.1", "@typescript-eslint/parser": "8.26.1", "@vitejs/plugin-react": "4.3.4", "eslint": "9.22.0", "eslint-config-prettier": "10.1.1", "eslint-config-react-app": "7.0.1", "eslint-plugin-react": "7.37.4", "eslint-plugin-react-hooks": "5.2.0", "eslint-plugin-react-refresh": "0.4.19", "globals": "16.0.0", "prettier": "3.0.3", "typescript": "5.0.4", "vite": "6.2.0" }, "resolutions": { "lodash": "4.17.21" }, "packageManager": "yarn@1.22.19" } ================================================ FILE: desktop/src/App/App.tsx ================================================ import { Box, Code, Container, Link, Text, VStack, useColorModeValue } from "@chakra-ui/react" import { useEffect, useMemo } from "react" import { Link as RouterLink, useMatch, useRouteError } from "react-router-dom" import { DevPodProvider, ProInstancesProvider, WorkspaceStore, WorkspaceStoreProvider, useChangeSettings, } from "../contexts" import { Routes } from "../routes" import { OSSApp } from "./OSSApp" import { ProApp } from "./ProApp" import { usePreserveLocation } from "./usePreserveLocation" import { ErrorBoundary } from "react-error-boundary" import { ErrorMessageBox } from "@/components" export function App() { const routeMatchPro = useMatch(`${Routes.PRO}/*`) usePreserveLocation() usePartyParrot() const store = useMemo(() => { if (routeMatchPro == null) { return new WorkspaceStore() } }, [routeMatchPro]) return ( ( )}> {routeMatchPro == null ? ( ) : ( )} ) } export function ErrorPage() { const error = useRouteError() const contentBackgroundColor = useColorModeValue("white", "background.darkest") return ( Whoops, something went wrong or this page doesn't exist. Go back to home {JSON.stringify(error, null, 2)}{" "} ) } function usePartyParrot() { const { set: setSettings, settings } = useChangeSettings() useEffect(() => { const handler = (event: KeyboardEvent) => { if (event.shiftKey && event.ctrlKey && event.key.toLowerCase() === "p") { const current = settings.partyParrot setSettings("partyParrot", !current) } } document.addEventListener("keyup", handler) return () => document.addEventListener("keyup", handler) }, [setSettings, settings.partyParrot]) } ================================================ FILE: desktop/src/App/Changelog.tsx ================================================ import { Box, Heading, Link, ListItem, UnorderedList } from "@chakra-ui/react" import Markdown from "markdown-to-jsx" import { client } from "../client" export type TLinkClickEvent = React.MouseEvent & { target: HTMLLinkElement } export type TChangeLogProps = Readonly<{ rawMarkdown: string }> export function Changelog({ rawMarkdown }: TChangeLogProps) { return ( { e.preventDefault() client.open(e.target.href) }, }, }, ul: { component: UnorderedList, }, li: { component: ListItem, }, }, }}> {rawMarkdown} ) } ================================================ FILE: desktop/src/App/OSSApp.tsx ================================================ import { client } from "@/client" import { QueryKeys } from "@/queryKeys" import { Box, Flex, Grid, GridItem, GridProps, HStack, LinkBox, LinkOverlay, Text, VStack, useColorModeValue, useToken, } from "@chakra-ui/react" import { useQuery } from "@tanstack/react-query" import { useEffect, useMemo } from "react" import { Outlet, Link as RouterLink, useMatch, useNavigate } from "react-router-dom" import { useBorderColor } from "../Theme" import { Notifications, ProSwitcher, Sidebar, SidebarMenuItem, StatusBar, Toolbar, } from "../components" import { SIDEBAR_WIDTH, STATUS_BAR_HEIGHT } from "../constants" import { ToolbarProvider, useProviders, useSettings } from "../contexts" import { Briefcase, Cog, Stack3D } from "../icons" import { isLinux, isMacOS } from "../lib" import { Routes } from "../routes" import { useWelcomeModal } from "../useWelcomeModal" import { showTitleBar, titleBarSafeArea } from "./constants" import { useAppReady } from "./useAppReady" export function OSSApp() { const navigate = useNavigate() const { errorModal, changelogModal, proLoginModal } = useAppReady() const rootRouteMatch = useMatch(Routes.ROOT) const { sidebarPosition } = useSettings() const contentBackgroundColor = useColorModeValue("white", "background.darkest") const actionHoverColor = useColorModeValue("gray.100", "gray.700") const toolbarHeight = useToken("sizes", showTitleBar ? "28" : "20") const borderColor = useBorderColor() const showTitle = isMacOS || isLinux const providerUpdateInfo = useProviderUpdates() const providerUpdateCount = providerUpdateInfo?.length ?? 0 const mainGridProps = useMemo(() => { if (sidebarPosition === "right") { return { templateAreas: `"main sidebar"`, gridTemplateColumns: `1fr ${SIDEBAR_WIDTH}` } } return { templateAreas: `"sidebar main"`, gridTemplateColumns: `${SIDEBAR_WIDTH} 1fr` } }, [sidebarPosition]) useEffect(() => { if (rootRouteMatch !== null) { navigate(Routes.WORKSPACES) } }, [navigate, rootRouteMatch]) const { modal: welcomeModal } = useWelcomeModal() return ( <> {showTitleBar && } }> Workspaces }> Providers }> Settings Routes.toAction(action.id)} badgeNumber={providerUpdateCount} providerUpdates={ providerUpdateInfo && providerUpdateCount > 0 && ( <> {providerUpdateInfo.map(({ providerName }) => ( Provider {providerName} Update available ))} ) } /> {welcomeModal} {errorModal} {changelogModal} {proLoginModal} ) } type TTitleBarProps = Readonly<{ showTitle?: boolean }> function TitleBar({ showTitle = true }: TTitleBarProps) { return ( {showTitle && ( DevPod )} ) } function useProviderUpdates() { const [[providers]] = useProviders() const { data: providerUpdateInfo } = useQuery({ // eslint-disable-next-line @tanstack/query/exhaustive-deps queryKey: QueryKeys.PROVIDERS_CHECK_UPDATE_ALL, queryFn: async () => { if (providers === undefined || Object.keys(providers).length === 0) { return } const results = await Promise.allSettled( Object.entries(providers) .filter(([, provider]) => !provider.isProxyProvider) .map(async ([p]) => ({ name: p, update: await client.providers.checkUpdate(p), })) ) return results .map((r) => { if (r.status !== "fulfilled" || r.value.update.err) { return null } if (!r.value.update.val.updateAvailable) { return null } return { providerName: r.value.name, updateAvailable: r.value.update.val.updateAvailable } }) .filter((r): r is Exclude => r !== null) }, refetchInterval: 1000 * 60 * 60 * 30, // 30 minutes staleTime: Infinity, }) return providerUpdateInfo } ================================================ FILE: desktop/src/App/ProApp.tsx ================================================ import { STATUS_BAR_HEIGHT } from "@/constants" import { ProviderProvider } from "@/contexts/DevPodContext/DevPodProvider" import { BellDuotone, CogDuotone, LockDuotone } from "@/icons" import { TConnectionStatus, useConnectionStatus } from "@/lib" import { QueryKeys } from "@/queryKeys" import { Routes } from "@/routes" import { TPlatformVersionInfo } from "@/types" import { Avatar, Box, Button, Divider, HStack, IconButton, Link, List, ListItem, Popover, PopoverArrow, PopoverContent, PopoverHeader, PopoverTrigger, Portal, Text, Tooltip, useColorModeValue, useDisclosure, } from "@chakra-ui/react" import { ManagementV1Self } from "@loft-enterprise/client/gen/models/managementV1Self" import { useQuery } from "@tanstack/react-query" import { ReactElement, ReactNode, cloneElement, useMemo } from "react" import { Outlet, Link as RouterLink, To } from "react-router-dom" import { Notifications, ProLayout, StatusBar, Toolbar } from "../components" import { ProInstancesProvider, ProProvider, ProWorkspaceStore, ToolbarProvider, WorkspaceStoreProvider, useProContext, useProHost, } from "../contexts" import { DaemonClient } from "@/client/pro/client" export function ProApp() { const host = useProHost() if (!host) { throw new Error("No host found. This shouldn't happen") } const store = useMemo(() => new ProWorkspaceStore(host), [host]) return ( ) } type TProAppContentProps = Readonly<{ host: string }> function ProAppContent({ host }: TProAppContentProps) { const { managementSelfQuery: selfQuery, client } = useProContext() const connectionStatus = useConnectionStatus() const versionInfo = usePlatformVersion() return ( {client instanceof DaemonClient ? ( <> Routes.toProWorkspace(host, action.targetID)} icon={ } /> ) : ( <> } /> Routes.toProWorkspace(host, action.targetID)} icon={ } /> )} } statusBarItems={ <> {/* The box is just here for tooltip to take a ref */} {versionInfo?.currentProviderVersion && ( {versionInfo.currentProviderVersion} {versionInfo.currentProviderVersion !== versionInfo.remoteProviderVersion ? `/${versionInfo.remoteProviderVersion}` : ""} )} {versionInfo?.serverVersion && ( {versionInfo.serverVersion} )} }> ) } function usePlatformVersion(): TPlatformVersionInfo | undefined { const { host, client } = useProContext() const { data } = useQuery({ queryKey: QueryKeys.versionInfo(host), queryFn: async () => { return (await client.getVersion()).unwrap() }, refetchInterval: 1_000 * 60, // every minute }) return data } type TConnectionStatusProps = Readonly<{ status: TConnectionStatus }> function ConnectionStatus({ status }: TConnectionStatusProps) { if (status.isLoading) { return null } const content = ( {status.healthy ? "Connected" : "Disconnected"} ) if (status.details && status.details.length > 0) { return ( {status.details.map((detail, i) => ( {detail} ))} ) }> {content} ) } return content } type TProfileMenuProps = Readonly<{ host: string self: ManagementV1Self | undefined }> function UserMenu({ host, self }: TProfileMenuProps) { const iconColor = useColorModeValue("primary.600", "primary.300") const { isOpen, onClose, onToggle } = useDisclosure() const userName = self?.status?.user?.displayName ?? self?.status?.user?.name ?? "Anonymous" return ( <> } /> {userName} {/* TODO: Implement when we need it UserLinkButton to={Routes.toProProfile(host)} icon={}> Profile */} }> Credentials }> Settings ) } type TUserLinkButton = Readonly<{ children: ReactNode; to: To; icon: ReactElement }> function UserLinkButton({ children, to, icon }: TUserLinkButton) { const clonedIcon = cloneElement(icon, { boxSize: 5 }) return ( ) } ================================================ FILE: desktop/src/App/constants.ts ================================================ import { BoxProps } from "@chakra-ui/react" import { isLinux, isMacOS, isWindows } from "../lib" export const showTitleBar = isMacOS || isLinux || isWindows export const titleBarSafeArea: BoxProps["height"] = showTitleBar ? isWindows ? "6" : "12" : 0 ================================================ FILE: desktop/src/App/index.ts ================================================ export { App, ErrorPage } from "./App" ================================================ FILE: desktop/src/App/useAppReady.tsx ================================================ import { QueryKeys } from "@/queryKeys" import { TProInstance } from "@/types" import { Button, HStack, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Text, useDisclosure, useToast, } from "@chakra-ui/react" import { useQuery } from "@tanstack/react-query" import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow" import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react" import { matchPath, useNavigate } from "react-router" import { client } from "../client" import { ErrorMessageBox } from "../components" import { WORKSPACE_SOURCE_BRANCH_DELIMITER, WORKSPACE_SOURCE_COMMIT_DELIMITER } from "../constants" import { startWorkspaceAction, useChangeSettings, useProInstances, useWorkspaceStore, } from "../contexts" import { exists, hasCapability, useLoginProModal } from "../lib" import { Routes } from "../routes" import { useChangelogModal } from "./useChangelogModal" export function useAppReady() { const [[proInstances]] = useProInstances() const { store } = useWorkspaceStore() const isReadyLockRef = useRef(false) const viewID = useId() const navigate = useNavigate() const toast = useToast() const { modal: errorModal, setFailedMessage } = useErrorModal() const { modal: changelogModal } = useChangelogModal(isReadyLockRef.current) const { modal: proLoginModal, handleOpenLogin: handleProLogin } = useLoginProModal() const { set: setSetting } = useChangeSettings() // auto-update pro providers in the background useQuery({ queryKey: QueryKeys.proProviderUpdates(proInstances), queryFn: async () => { if (!proInstances || proInstances.length === 0) { return null } // let pro client check for updates without using the provider // we don't really care about the result in the context of the GUI, just need to make sure it's updating await Promise.allSettled( proInstances .filter( (instance) => instance.provider && instance.host && hasCapability(instance, "update-provider") ) .map(async (instance) => { const proClient = client.getProClient(instance) const checkUpdateRes = await proClient.checkUpdate() if (checkUpdateRes.err) { client.log( "error", `[${instance.host ?? ""}] Failed to check for upgrade: ${ checkUpdateRes.val.message }` ) return null } const { available: updateAvailable, newVersion } = checkUpdateRes.val if (!updateAvailable || !newVersion) { return null } client.log( "info", `[${ instance.host ?? "" }] New version available (${newVersion}). Attempting to update.` ) const updateRes = await proClient.update(newVersion) if (updateRes.err) { client.log( "error", `[${instance.host ?? ""}] Failed to upgrade: ${updateRes.val.message}` ) return null } client.log("info", `[${instance.host ?? ""}] Successfully updated to ${newVersion}`) }) ) return null }, enabled: proInstances && proInstances.length > 0, refetchInterval: 1_000 * 60 * 5, // 5 minutes }) useEffect(() => { window.addEventListener("contextmenu", (e) => { e.preventDefault() return false }) }, []) const handleMessage: Parameters[1] = useCallback( async (event) => { if (event.type === "ShowDashboard") { if (await getCurrentWebviewWindow().isMinimized()) { await getCurrentWebviewWindow().unminimize() } if (!(await getCurrentWebviewWindow().isVisible())) { await getCurrentWebviewWindow().show() } await getCurrentWebviewWindow().setFocus() return } if (event.type === "ShowToast") { await getCurrentWebviewWindow().setFocus() toast({ title: event.title, description: event.message, status: event.status, duration: 5_000, isClosable: true, }) return } if (event.type === "CommandFailed") { await getCurrentWebviewWindow().setFocus() const message = Object.entries(event) .filter(([key]) => key !== "type") .map(([key, value]) => `${key}: ${value}`) .join("\n") setFailedMessage(message) return } if (event.type === "LoginRequired") { const proInstances = await client.pro.listProInstances() if (proInstances.err) { return } const existingInstance = proInstances.val.find((i) => i.host === event.host) if (!existingInstance) { return } await getCurrentWebviewWindow().setFocus() const match = matchPath(Routes.toProInstance(event.host), location.pathname) if (match != null) { // only show toast if we're not on pro instance page anyway return } toast({ title: "Login Required", description: ( You have been logged out. Please log back in. ), status: "warning", duration: 5_000, isClosable: true, }) return } if (event.type === "SetupPro") { // check if host is already taken. If not, set window to foreground and pass evnet to pro login handler const proInstances = await client.pro.listProInstances() if (proInstances.err) { return } const existingInstance = proInstances.val.find((i) => i.host === event.host) if (existingInstance) { // only warn in console, don't show modal console.warn("Pro instance already exists", existingInstance) return } const data: Parameters[0] = { host: event.host, suggestedOptions: {}, } if (event.accessKey) { data.accessKey = event.accessKey } if (event.options) { data.suggestedOptions = event.options } await getCurrentWebviewWindow().setFocus() // ensure pro is enabled setSetting("experimental_devPodPro", true) handleProLogin(data) return } if (event.type === "OpenProInstance") { const proInstances = await client.pro.listProInstances() if (proInstances.err) { return } const existingInstance = proInstances.val.find((i) => i.host === event.host) if (!existingInstance?.host) { return } await getCurrentWebviewWindow().setFocus() navigate(Routes.toProInstance(existingInstance.host)) return } if (event.type === "ImportWorkspace") { await getCurrentWebviewWindow().setFocus() // Do we already know the workspace? let workspacesResult = await client.workspaces.listAll(false) if (workspacesResult.err) { const cleanedMsg = workspacesResult.val.message.split("\n").at(-1) ?? "" setFailedMessage("Failed to list workspaces: " + cleanedMsg) return } let maybeWorkspace = workspacesResult.val.find((w) => w.id === event.workspace_id) // Is it a pro workspace? if (maybeWorkspace && maybeWorkspace.provider?.name) { const proInstance = await findProInstance(maybeWorkspace.provider.name) if (proInstance && proInstance.host) { navigate(Routes.toProWorkspace(proInstance.host, maybeWorkspace.id)) return } } // At this point it can't be a new pro workspace anymore, // we'll have to go through the old import flow const importResult = await client.pro.importWorkspace({ workspaceID: event.workspace_id, workspaceUID: event.workspace_uid, devPodProHost: event.devpod_pro_host, project: event.project, options: event.options, }) if (importResult.err) { const cleanedMsg = importResult.val.message.split("\n").at(-1) ?? "" setFailedMessage("Failed to import workspace: " + cleanedMsg) return } workspacesResult = await client.workspaces.listAll(false) if (workspacesResult.err) { return } maybeWorkspace = workspacesResult.val.find((w) => w.id === event.workspace_id) if (!maybeWorkspace) { setFailedMessage("Could not find workspace after import") return } const actionID = startWorkspaceAction({ workspaceID: maybeWorkspace.id, streamID: viewID, config: { id: maybeWorkspace.id, providerConfig: { providerID: maybeWorkspace.provider?.name ?? undefined, }, ideConfig: { name: maybeWorkspace.ide?.name, }, }, store, }) navigate(Routes.toAction(actionID)) return } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (event.type === "OpenWorkspace") { const workspacesResult = await client.workspaces.listAll(false) if (workspacesResult.err) { return } // Try to find workspace by source let maybeWorkspace = workspacesResult.val.find((w) => { if (!w.source) { return false } // Check `repo@sha256:commitHash` if ( `${w.source.gitRepository ?? ""}${WORKSPACE_SOURCE_COMMIT_DELIMITER}${ w.source.gitCommit ?? "" }` === event.source ) { return true } // Check `repo@branch` if ( `${w.source.gitRepository ?? ""}${WORKSPACE_SOURCE_BRANCH_DELIMITER}${ w.source.gitBranch ?? "" }` === event.source ) { return true } // Check Git repo if (w.source.gitRepository === event.source) { return true } // Check local folder if (w.source.localFolder === event.source) { return true } // Check Docker Image if (w.source.image === event.source) { return true } return false }) // If we don't have a workspace by now, `source` isn't defined but `workspace_id` is, try to find workspace by ID // This happens for example if the message is triggered by a system tray item // WARN: `event.source` can be an empty string here, hence the falsy check if (maybeWorkspace === undefined && !event.source && exists(event.workspace_id)) { maybeWorkspace = workspacesResult.val.find((w) => w.id === event.workspace_id) } const ides = await client.ides.listAll() let defaultIDE = undefined if (ides.ok) { defaultIDE = ides.val.find((ide) => ide.default)?.name } const providerName = maybeWorkspace?.provider?.name if (maybeWorkspace !== undefined && providerName) { const proInstance = await findProInstance(providerName) if (proInstance && proInstance.host) { navigate(Routes.toProWorkspace(proInstance.host, maybeWorkspace.id)) return } const actionID = startWorkspaceAction({ workspaceID: maybeWorkspace.id, streamID: viewID, config: { id: maybeWorkspace.id, providerConfig: { providerID: providerName }, ideConfig: { name: defaultIDE ?? maybeWorkspace.ide?.name ?? null }, }, store, }) navigate(Routes.toAction(actionID)) return } const match = matchPath(Routes.PRO_INSTANCE, location.pathname) if (match && match.params.host) { navigate(Routes.toProWorkspaceCreate(match.params.host)) return } navigate( Routes.toWorkspaceCreate({ workspaceID: event.workspace_id, providerID: event.provider_id, rawSource: event.source, ide: event.ide, }) ) } }, [handleProLogin, navigate, setFailedMessage, setSetting, store, toast, viewID] ) // notifies underlying layer that ui is ready for communication useEffect(() => { const unsubscribePromise = client.subscribe("event", handleMessage) if (!isReadyLockRef.current) { isReadyLockRef.current = true unsubscribePromise.then(async () => { try { await client.ready() } catch (err) { return console.error(err) } }) } return () => { unsubscribePromise.then((unsubscribe) => { unsubscribe() }) } }, [handleMessage, navigate]) return { errorModal, changelogModal, proLoginModal } } function useErrorModal() { const [failedMessage, setFailedMessage] = useState(null) const { isOpen, onClose, onOpen } = useDisclosure() const modal = useMemo(() => { return ( setFailedMessage(null)} isCentered> {/* todo: customize the header */} Failed to open workspace from URL ) }, [isOpen, onClose, failedMessage]) useEffect(() => { if (failedMessage !== null) { onOpen() } else { onClose() } }, [onClose, onOpen, failedMessage]) return { modal, handleOpen: onOpen, setFailedMessage } } async function findProInstance(providerName: string): Promise { const providersRes = await client.providers.listAll() if (providersRes.err) return null const provider = providersRes.val[providerName] if (!provider || !provider.isProxyProvider) return null // handle pro provider const proInstanceRes = await client.pro.listProInstances() if (proInstanceRes.err) return null const proInstance = proInstanceRes.val.find( (proInstance) => proInstance.provider === providerName ) if (!proInstance?.host) return null return proInstance } ================================================ FILE: desktop/src/App/useChangelogModal.tsx ================================================ import { Button, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Text, useDisclosure, } from "@chakra-ui/react" import { useEffect, useMemo, useState } from "react" import { Release } from "../gen" import { useReleases, useVersion } from "../lib" import { Changelog } from "./Changelog" const LAST_INSTALLED_VERSION_KEY = "devpod-last-installed-version" export function useChangelogModal(isReady: boolean) { const currentVersion = useVersion() const releases = useReleases() const { isOpen, onClose, onOpen } = useDisclosure() const [latestRelease, setLatestRelease] = useState(null) const modal = useMemo( () => latestRelease !== null ? ( Changelog {latestRelease.body ? ( ) : ( This release doesn't have a changelog )} ) : null, [isOpen, latestRelease, onClose] ) useEffect(() => { if (!isReady || !currentVersion || !releases) { return } const latestVersion = localStorage.getItem(LAST_INSTALLED_VERSION_KEY) const maybeRelease = releases.find((r) => r.tag_name === `v${currentVersion}`) if (latestVersion !== currentVersion) { localStorage.setItem(LAST_INSTALLED_VERSION_KEY, currentVersion) if (maybeRelease !== undefined && !maybeRelease.name?.endsWith("[skip changelog]")) { setLatestRelease(maybeRelease) onOpen() } } }, [currentVersion, isReady, onOpen, releases]) return { modal } } ================================================ FILE: desktop/src/App/usePreserveLocation.tsx ================================================ import { useEffect } from "react" import { Location, matchRoutes, useLocation } from "react-router" import { client } from "../client" import { LocalStorageBackend, Store } from "../lib" import { Routes } from "@/routes" const LOCATION_KEY = "location" const CURRENT_LOCATION_KEY = "current" type TLocationStore = { [CURRENT_LOCATION_KEY]: Location } const store = new Store(new LocalStorageBackend(LOCATION_KEY)) export function usePreserveLocation() { const location = useLocation() useEffect(() => { // only save location for these routes const match = matchRoutes( [ { path: Routes.ROOT }, { path: Routes.PROVIDER }, { path: Routes.PROVIDERS }, { path: Routes.WORKSPACES }, { path: Routes.PRO_INSTANCE }, ], location ) if (match == null) { return } try { store.set(CURRENT_LOCATION_KEY, location) } catch (err) { client.log("error", `Failed to serialize location: ${err}`) } }, [location]) } ================================================ FILE: desktop/src/ProRoot.tsx ================================================ import { Outlet } from "react-router-dom" export function ProRoot() { return } ================================================ FILE: desktop/src/Theme/ThemeProvider.tsx ================================================ import { ChakraProvider, Theme, ToastProviderProps, extendTheme } from "@chakra-ui/react" import { ReactNode, useEffect, useState } from "react" import { TSettings, useSettings } from "../contexts" import { theme as initialTheme } from "./theme" const toastOptions: ToastProviderProps = { defaultOptions: { variant: "subtle" } } const fontSize: Record = { sm: "12px", md: "14px", lg: "16px", xl: "18px", } export function ThemeProvider({ children }: Readonly<{ children?: ReactNode }>) { const settings = useSettings() const [theme, setTheme] = useState(initialTheme) useEffect(() => { setTheme( (current) => extendTheme( { styles: { global: { html: { fontSize: fontSize[settings.zoom], }, }, }, }, current ) as Theme ) }, [settings]) return ( {children} ) } ================================================ FILE: desktop/src/Theme/button.ts ================================================ import { defineStyleConfig } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" import { theme as defaultTheme } from "@chakra-ui/theme" export const Button = defineStyleConfig({ defaultProps: { size: "sm", }, variants: { primary(props) { return { color: mode("white", "gray.50")(props), borderColor: "primary.600", _dark: { borderColor: "primary.500", backgroundColor: "primary.600", }, borderWidth: 1, backgroundColor: "primary.500", _hover: { backgroundColor: "primary.600", _disabled: { background: "primary.500", }, _dark: { backgroundColor: "primary.700", }, }, } }, outline(props) { return { borderColor: props.colorScheme == "primary" ? "primary.600" : "gray.200", _dark: { borderColor: props.colorScheme == "primary" ? "primary.200" : "gray.700", }, _hover: { _dark: { bg: props.colorScheme == "primary" ? "" : "gray.700", }, }, _active: { _dark: { bg: props.colorScheme == "primary" ? "" : "gray.800", }, }, } }, solid(props) { let bgDark = "gray.800" if (props.colorScheme === "primary") { bgDark = "" } else { bgDark = defaultTheme.components.Button.variants?.solid(props).bg ?? "" } let bgHoverDark = "gray.700" if (props.colorScheme === "primary") { bgHoverDark = "" } else { bgHoverDark = defaultTheme.components.Button.variants?.solid(props)._hover.bg ?? "" } let bgActiveDark = "gray.700" if (props.colorScheme === "primary") { bgActiveDark = "" } else { bgActiveDark = defaultTheme.components.Button.variants?.solid(props)._active.bg ?? "" } return { _dark: { bg: bgDark, }, _hover: { _dark: { bg: bgHoverDark, }, }, _active: { _dark: { bg: bgActiveDark, }, }, } }, ["solid-outline"](props) { return { color: mode("gray.800", "gray.100")(props), borderColor: mode("gray.200", "gray.700")(props), borderWidth: 1, ".chakra-button__group[data-attached][data-orientation=horizontal] > &:not(:last-of-type)": { marginEnd: "-1px" }, ".chakra-button__group[data-attached][data-orientation=vertical] > &:not(:last-of-type)": { marginBottom: "-1px", }, backgroundColor: mode("gray.100", "gray.800")(props), _hover: { backgroundColor: mode("gray.200", "gray.700")(props), }, _active: { backgroundColor: mode("gray.300", "gray.900")(props), }, } }, announcement({ theme }) { const from = theme.colors.primary["900"] const to = theme.colors.primary["600"] return { color: "white", transition: "background 150ms", fontWeight: "regular", background: `linear-gradient(170deg, ${from} 15%, ${to})`, backgroundSize: "130% 130%", _hover: { backgroundPosition: "90% 50%", }, _active: { boxShadow: "inset 0 0 3px 2px rgba(0, 0, 0, 0.2)", }, } }, proWorkspaceIDE(_props) { return { color: "primary.900", fontWeight: "semibold", bg: "primary.100", _hover: { bg: "primary.200", }, _active: { bg: "primary.300", }, } }, ghost(props) { return { _hover: { _dark: { bg: props.colorScheme == "primary" ? "" : "gray.700", }, }, _active: { _dark: { bg: props.colorScheme == "primary" ? "" : "gray.800", }, }, } }, }, }) ================================================ FILE: desktop/src/Theme/card.ts ================================================ import { cardAnatomy } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(cardAnatomy.keys) export const Card = defineMultiStyleConfig({ baseStyle: definePartsStyle((props) => { return { container: { backgroundColor: "gray.50", borderColor: mode("gray.200", "gray.700")(props), _dark: { backgroundColor: "gray.900", }, }, } }), }) ================================================ FILE: desktop/src/Theme/checkbox.ts ================================================ import { checkboxAnatomy } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers } from "@chakra-ui/react" const { defineMultiStyleConfig } = createMultiStyleConfigHelpers(checkboxAnatomy.keys) export const Checkbox = defineMultiStyleConfig({ defaultProps: { colorScheme: "primary", }, baseStyle: { container: { borderColor: "gray.200", _dark: { borderColor: "gray.700", }, }, }, }) ================================================ FILE: desktop/src/Theme/form.ts ================================================ import { formAnatomy } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(formAnatomy.keys) export const Form = defineMultiStyleConfig({ baseStyle: definePartsStyle((props) => { return { helperText: { color: mode("gray.500", "gray.300")(props), }, } }), variants: { contrast: definePartsStyle((props) => { return { helperText: { color: mode("gray.600", "gray.300")(props), }, } }), }, }) ================================================ FILE: desktop/src/Theme/index.ts ================================================ export { ThemeProvider } from "./ThemeProvider" export { useBorderColor } from "./themeHooks" ================================================ FILE: desktop/src/Theme/input.ts ================================================ import { inputAnatomy } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers } from "@chakra-ui/react" const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers( inputAnatomy.keys ) export const Input = defineMultiStyleConfig({ variants: { outline: definePartsStyle(() => { return { addon: { borderColor: "gray.200", _dark: { borderColor: "gray.800", }, }, field: { borderColor: "gray.200", _dark: { borderColor: "gray.800", }, }, } }), }, }) ================================================ FILE: desktop/src/Theme/menu.ts ================================================ import { menuAnatomy } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(menuAnatomy.keys) export const Menu = defineMultiStyleConfig({ baseStyle: definePartsStyle((props) => { return { item: { fontSize: "sm", bg: mode("white", "gray.900")(props), _selected: { bg: mode("gray.200", "gray.800")(props), }, _hover: { bg: mode("gray.100", "gray.700")(props), }, borderColor: "gray.200", _dark: { borderColor: "gray.700", }, }, list: { bg: mode("white", "gray.900")(props), borderColor: "gray.200", _dark: { borderColor: "gray.700", }, }, divider: { borderColor: "gray.200", _dark: { borderColor: "gray.700", }, }, } }), }) ================================================ FILE: desktop/src/Theme/modal.ts ================================================ import { modalAnatomy } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers( modalAnatomy.keys ) export const Modal = defineMultiStyleConfig({ baseStyle: definePartsStyle((props) => { return { body: { bg: mode("white", "gray.900")(props), }, dialog: { bg: mode("white", "gray.900")(props), }, } }), }) ================================================ FILE: desktop/src/Theme/popover.ts ================================================ import { popoverAnatomy } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers, cssVar } from "@chakra-ui/react" const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers( popoverAnatomy.keys ) export const Popover = defineMultiStyleConfig({ baseStyle: definePartsStyle((props) => { const theme = props.theme let bg = theme.colors.white if (props.colorMode == "dark") { bg = theme.colors.gray["900"] } return { content: { borderColor: "gray.200", bg, boxShadow: theme.shadows.xl, _focusVisible: { outline: "2px solid transparent", outlineOffset: "2px", boxShadow: theme.shadows.xl, }, [cssVar("popper-bg").variable]: bg, _dark: { [cssVar("popper-arrow-bg").variable]: bg, borderColor: "gray.700", }, }, arrow: { bg }, popper: { zIndex: theme.zIndices.popover, bg, }, header: { display: "flex", alignItems: "center", flexFlow: "row nowrap", padding: 4, spacing: 0, justifyContent: "space-between", borderBottomWidth: "thin", borderColor: "inherit", fontWeight: "bold", p: { fontWeight: "normal", }, }, } }), }) ================================================ FILE: desktop/src/Theme/radio.ts ================================================ import { radioAnatomy } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers } from "@chakra-ui/react" const { defineMultiStyleConfig } = createMultiStyleConfigHelpers(radioAnatomy.keys) export const Radio = defineMultiStyleConfig({ defaultProps: { colorScheme: "primary", }, }) ================================================ FILE: desktop/src/Theme/select.ts ================================================ import { selectAnatomy } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers } from "@chakra-ui/react" const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers( selectAnatomy.keys ) export const Select = defineMultiStyleConfig({ baseStyle: definePartsStyle({ addon: { borderColor: "gray.200", _dark: { borderColor: "gray.700", }, }, field: { borderColor: "gray.200", _dark: { borderColor: "gray.700", }, }, }), variants: { outline: definePartsStyle(() => { return { addon: { border: "", borderWidth: "thin", borderStyle: "solid", borderColor: "gray.200", _dark: { borderColor: "gray.700", }, }, field: { border: "", borderWidth: "thin", borderStyle: "solid", borderColor: "gray.200", _dark: { borderColor: "gray.700", }, }, } }), }, }) ================================================ FILE: desktop/src/Theme/switch.ts ================================================ import { switchAnatomy } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers } from "@chakra-ui/react" const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers( switchAnatomy.keys ) export const Switch = defineMultiStyleConfig({ baseStyle: definePartsStyle(({ theme }) => { const from = theme.colors.primary["400"] const to = theme.colors.primary["800"] return { track: { _checked: { background: `linear-gradient(90deg, ${from} 30%, ${to})`, }, }, } }), }) ================================================ FILE: desktop/src/Theme/tabs.ts ================================================ import { tabsAnatomy } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(tabsAnatomy.keys) const muted = definePartsStyle((props) => { return { tab: { bg: mode("white", "black")(props), fontWeight: "medium", _selected: { bg: mode("gray.100", "gray.900")(props), }, _hover: { bg: mode("gray.50", "gray.700")(props), }, }, tablist: { width: "fit-content", borderWidth: "thin", borderColor: "inherit", borderRadius: "md", "> *:not(:last-child, :first-child)": { borderRightWidth: "thin", borderRightColor: "inherit", borderRadius: "0", }, "> :first-child": { borderTopLeftRadius: "md", borderBottomLeftRadius: "md", borderRightWidth: "thin", borderRightColor: "inherit", }, "> :last-child": { borderTopRightRadius: "md", borderBottomRightRadius: "md", }, }, } }) const enclosed = definePartsStyle((props) => { return { tab: { fontWeight: "medium", _selected: { color: mode("primary.600", "primary.300")(props), }, _hover: { bg: mode("gray.50", "gray.800")(props), }, }, } }) const mutedPopover = definePartsStyle((props) => { return { tab: { bg: mode("white", "gray.900")(props), fontWeight: "medium", _selected: { bg: mode("gray.100", "gray.800")(props), }, _hover: { bg: mode("gray.50", "gray.700")(props), }, }, tablist: { width: "fit-content", borderWidth: "thin", borderColor: "inherit", borderRadius: "md", "> *:not(:last-child, :first-child)": { borderRightWidth: "thin", borderRightColor: "inherit", borderRadius: "0", }, "> :first-child": { borderTopLeftRadius: "md", borderBottomLeftRadius: "md", borderRightWidth: "thin", borderRightColor: "inherit", }, "> :last-child": { borderTopRightRadius: "md", borderBottomRightRadius: "md", }, }, } }) const variants = { muted, enclosed, "muted-popover": mutedPopover, } export const Tabs = defineMultiStyleConfig({ variants }) ================================================ FILE: desktop/src/Theme/tag.ts ================================================ import { tagAnatomy } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(tagAnatomy.keys) export const Tag = defineMultiStyleConfig({ baseStyle: definePartsStyle((props) => { return { container: { bg: mode("gray.200", "gray.700")(props), color: mode("gray.800", "gray.100")(props), }, } }), variants: { ghost: definePartsStyle(() => { return { container: { bg: "transparent", }, } }), }, }) ================================================ FILE: desktop/src/Theme/text.ts ================================================ import { defineStyleConfig } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" export const Text = defineStyleConfig({ variants: { muted(props) { return { color: mode("gray.600", "gray.400")(props), } }, }, }) ================================================ FILE: desktop/src/Theme/textarea.ts ================================================ import { defineStyleConfig } from "@chakra-ui/react" export const Textarea = defineStyleConfig({ variants: { outline: { borderColor: "gray.200", _dark: { borderColor: "gray.800", }, }, }, }) ================================================ FILE: desktop/src/Theme/theme.ts ================================================ import { Theme, ThemeOverride, Tooltip, defineStyleConfig, extendTheme } from "@chakra-ui/react" import { mode } from "@chakra-ui/theme-tools" import { Button } from "./button" import { Card } from "./card" import { Checkbox } from "./checkbox" import { Form } from "./form" import { Input } from "./input" import { Menu } from "./menu" import { Modal } from "./modal" import { Popover } from "./popover" import { Radio } from "./radio" import { Select } from "./select" import { Switch } from "./switch" import { Tabs } from "./tabs" import { Tag } from "./tag" import { Text } from "./text" import { Textarea } from "./textarea" const Code = defineStyleConfig({ variants: { decorative(props) { return { backgroundColor: "primary.400", color: mode("white", "gray.900")(props), } }, }, }) const Link = defineStyleConfig({ defaultProps: { variant: "primary", }, variants: { muted(props) { return { color: mode("gray.600", "gray.400")(props) } }, primary(props) { const primary = props.theme.colors.primary return { color: mode(primary["800"], primary["400"])(props) } }, }, }) const FormError = defineStyleConfig({ baseStyle: { text: { userSelect: "text", cursor: "text", }, }, }) const TooltipTheme = defineStyleConfig({ baseStyle(props) { return { bg: mode("gray.800", "gray.200")(props), color: mode("gray.100", "gray.800")(props), } }, }) // It's ugly but it works: https://github.com/chakra-ui/chakra-ui/issues/1424#issuecomment-743342944 // Unfortunately there is no other way of overring the default placement. Tooltip.defaultProps = { ...Tooltip.defaultProps, placement: "top" } export const theme = extendTheme({ styles: { global({ colorMode }) { return { html: { fontSize: "14px", overflow: "hidden", background: "transparent", position: "fixed", }, body: { background: "transparent", userSelect: "none", cursor: "default", }, td: { userSelect: "text", }, code: { userSelect: "text", cursor: "text", }, "input::placeholder": { color: colorMode === "light" ? "gray.500" : "gray.400", }, } }, }, colors: { primary: { 50: "#F8EFFF", 100: "#F0DFFF", 200: "#D8ABFF", 300: "#BF76FF", 400: "#B157FF", 500: "#A640FF", 600: "#9B29FF", 700: "#8600DC", 800: "#7100B9", 900: "#40006A", }, gray: { 50: "#F7F5F9", 100: "#ECE8F0", 200: "#DCD6E1", 300: "#C5BFC9", 400: "#ABA5B0", 500: "#948E99", 600: "#7C7581", 700: "#655E69", 800: "#4A464D", 900: "#2C2630", }, text: { secondary: "#465E75", tertiary: "#5C7997", }, divider: { main: "#B0C3D6", light: "#DCE5EE", dark: "#465E75", }, background: { darkest: "rgb(16, 18, 20)", }, }, config: { initialColorMode: "system", useSystemColorMode: true, }, components: { Button, Card, Code, Menu, Switch, Tabs, Checkbox, Radio, Link, Form, FormError, Popover, Modal, Tag, Input, Select, Text, Textarea, Tooltip: TooltipTheme, }, } satisfies ThemeOverride) as Theme ================================================ FILE: desktop/src/Theme/themeHooks.tsx ================================================ import { useColorModeValue } from "@chakra-ui/react" export function useBorderColor() { return useColorModeValue("gray.200", "gray.700") } ================================================ FILE: desktop/src/client/client.ts ================================================ import { UseToastOptions } from "@chakra-ui/react" import { app, event, path } from "@tauri-apps/api" import { invoke } from "@tauri-apps/api/core" import { Theme as TauriTheme, getCurrentWindow } from "@tauri-apps/api/window" import * as clipboard from "@tauri-apps/plugin-clipboard-manager" import * as dialog from "@tauri-apps/plugin-dialog" import * as fs from "@tauri-apps/plugin-fs" import * as log from "@tauri-apps/plugin-log" import * as os from "@tauri-apps/plugin-os" import * as process from "@tauri-apps/plugin-process" import * as shell from "@tauri-apps/plugin-shell" import { Command } from "@tauri-apps/plugin-shell" import * as updater from "@tauri-apps/plugin-updater" import { TSettings } from "../contexts" import { Release } from "../gen" import { Result, Return, hasCapability, isError, noop } from "../lib" import { TCommunityContributions, TProInstance, TUnsubscribeFn } from "../types" import { Command as DevPodCommand } from "./command" import { ContextClient } from "./context" import { IDEsClient } from "./ides" import { ProClient } from "./pro" import { DaemonClient } from "./pro/client" import { ProvidersClient } from "./providers" import { TAURI_SERVER_URL } from "./tauriClient" import { WorkspacesClient } from "./workspaces" // These types have to match the rust types! Make sure to update them as well! type TChannels = { event: | Readonly<{ type: "ShowToast" message: string title: string status: NonNullable }> | Readonly<{ type: "ShowDashboard" }> | Readonly<{ type: "CommandFailed" }> | Readonly<{ type: "OpenWorkspace" workspace_id: string | null provider_id: string | null ide: string | null source: string }> | Readonly<{ type: "ImportWorkspace" workspace_id: string workspace_uid: string devpod_pro_host: string project: string options: Record | null }> | Readonly<{ type: "SetupPro" host: string accessKey: string | null options: Record | null }> | Readonly<{ type: "OpenProInstance" host: string | null }> | Readonly<{ type: "LoginRequired" host: string provider: string }> } type TChannelName = keyof TChannels type TClientEventListener = (payload: TChannels[TChannel]) => void type TClientSettings = Pick< TSettings, | "debugFlag" | "additionalCliFlags" | "dotfilesUrl" | "additionalEnvVars" | "sshKeyPath" | "httpProxyUrl" | "httpsProxyUrl" | "noProxy" > export type TPlatform = Awaited> export type TArch = Awaited> class Client { public readonly workspaces = new WorkspacesClient() public readonly providers = new ProvidersClient() public readonly ides = new IDEsClient() public readonly context = new ContextClient() public readonly pro = new ProClient("") public setSetting( name: TSettingName, value: TSettings[TSettingName] ) { if (name === "debugFlag") { const debug: boolean = value as boolean this.workspaces.setDebug(debug) this.providers.setDebug(debug) this.ides.setDebug(debug) this.pro.setDebug(debug) } if (name === "additionalCliFlags") { this.workspaces.setAdditionalFlags(value as string) } if (name === "dotfilesUrl") { this.workspaces.setDotfilesFlag(value as string) } if (name === "sshKeyPath") { this.workspaces.setSSHKeyPath(value as string) } if (name === "additionalEnvVars") { DevPodCommand.ADDITIONAL_ENV_VARS = value as string } if (name === "httpProxyUrl") { DevPodCommand.HTTP_PROXY = value as string } if (name === "httpsProxyUrl") { DevPodCommand.HTTPS_PROXY = value as string } if (name === "noProxy") { DevPodCommand.NO_PROXY = value as string } } public ready(): Promise { return invoke("ui_ready") } public async subscribe( channel: T, listener: TClientEventListener ): Promise { // `TClient` is strictly typed so we're fine casting the response as `any`. try { const unsubscribe = await event.listen(channel, (event) => { listener(event.payload) }) return unsubscribe } catch { return noop } } // emitEvent publishes to a given channel and invokes the corresponding handler. // This is only intended to be used for debugging right now. public emitEvent(e: TChannels[T]) { event.emit("event", e) } public fetchPlatform(): TPlatform { return os.platform() } public pathSeparator(): string { return path.sep() } public fetchArch(): TArch { return os.arch() } public fetchVersion(): Promise { return app.getVersion() } public async fetchCommunityContributions(): Promise> { try { const contributions = await invoke("get_contributions") return Return.Value(contributions) } catch (e) { if (isError(e)) { return Return.Failed(e.message) } const errMsg = "Unable to fetch community contributions" if (typeof e === "string") { return Return.Failed(`${errMsg}: ${e}`) } return Return.Failed(errMsg) } } public async fetchReleases(): Promise> { try { // WARN: This is a workaround for a memory leak in tauri, see https://github.com/tauri-apps/tauri/issues/4026 for more details. // tl;dr tauri doesn't release the memory in it's invoke api properly which is specially noticeable with larger payload, like the releases. const res = await fetch(TAURI_SERVER_URL + "/releases") if (!res.ok) { return Return.Failed(`Fetch releases: ${res.statusText}`) } const releases = (await res.json()) as readonly Release[] return Return.Value(releases) } catch (e) { // return empty list if error during development if (isError(e)) { return Return.Failed(e.message) } const errMsg = "Unable to fetch releases" if (typeof e === "string") { return Return.Failed(`${errMsg}: ${e}`) } return Return.Failed(errMsg) } } public async getDir( dir: Extract | "SSH" ): Promise { switch (dir) { case "AppData": { return path.appDataDir() } case "AppLog": { return await path.appLogDir() } case "Home": { return await path.homeDir() } case "SSH": { return await path.join(await path.homeDir(), ".ssh") } } } public async openDir( dir: Extract ): Promise { try { let p = await this.getDir(dir) if (dir === "AppLog") { p = await path.join(p, "DevPod.log") } shell.open(p) } catch { // noop for now } } public async selectFromDir(title?: string): Promise { return dialog.open({ title, directory: true, multiple: false }) } public async selectFileYaml(): Promise { return dialog.open({ filters: [{ name: "yaml", extensions: ["yml", "yaml"] }], directory: false, multiple: false, }) } public async selectFile(defaultPath?: string): Promise { return dialog.open({ directory: false, multiple: false, defaultPath }) } public async copyFile(src: string, dest: string): Promise { return fs.copyFile(src, dest) } public async copyFilePaths(src: string[], dest: string[]) { return this.copyFile(await path.join(...src), await path.join(...dest)) } public async writeTextFile(targetPath: string[], data: string) { return fs.writeTextFile(await path.join(...targetPath), data) } public async readFile(targetPath: string[]) { return fs.readFile(await path.join(...targetPath)) } public async readTextFile(targetPath: string[]) { return fs.readTextFile(await path.join(...targetPath)) } public async writeFile(targetPath: string[], data: Uint8Array) { return fs.writeFile(await path.join(...targetPath), data) } public async installCLI(force: boolean = false): Promise> { try { await invoke("install_cli", { force }) return Return.Ok() } catch (e) { if (isError(e)) { return Return.Failed(e.message) } if (typeof e === "string") { return Return.Failed(`Failed to install CLI: ${e}`) } return Return.Failed("Unable to install CLI") } } public async getEnv(name: string): Promise { return invoke("get_env", { name }) } public async isCLIInstalled(): Promise> { try { // we're in a flatpak, we need to check in other paths. const isFlatpak = await this.getEnv("FLATPAK_ID") if (isFlatpak) { this.log("debug", "Running in flatpak, checking ~/.local/bin on the host"); const home_dir = await this.getEnv("HOME") // this will throw if doesn't exist const exists = await invoke("file_exists", { filepath: home_dir + "/.local/bin/devpod", }) return Return.Value(exists) } const result = await Command.create("run-path-devpod-cli", ["version"]).execute() if (result.code !== 0) { return Return.Value(false) } return Return.Value(true) } catch { return Return.Value(false) } } public open(link: string): void { shell.open(link) } public async quit(): Promise> { try { await process.exit(0) return Return.Ok() } catch { return Return.Failed("Unable to quit") } } public async writeToClipboard(data: string): Promise> { try { await clipboard.writeText(data) return Return.Ok() } catch (e) { return Return.Failed(`Unable to write to clipboard: ${e}`) } } public async checkUpdates(): Promise> { try { const isOk = await invoke("check_updates") return Return.Value(isOk) } catch (e) { return Return.Failed(`${e}`) } } public async fetchPendingUpdate(): Promise> { try { const release = await invoke("get_pending_update") return Return.Value(release) } catch (e) { return Return.Failed(`${e}`) } } public async installUpdate(): Promise> { try { const update = await updater.check() if (!update) { return Return.Ok() } await update.install() return Return.Ok() } catch (e) { return Return.Failed(`${e}`) } } public async restart(): Promise { await process.relaunch() } public async closeCurrentWindow(): Promise { await getCurrentWindow().close() } public async getSystemTheme(): Promise { return getCurrentWindow().theme() } public log(level: "debug" | "info" | "warn" | "error", message: string) { const logFn = log[level] logFn(message) } public getProClient(proInstance: TProInstance): ProClient | DaemonClient { if (hasCapability(proInstance, "daemon")) { return new DaemonClient(proInstance.host!) } else { return new ProClient(proInstance.host!) } } } // Singleton client export const client = new Client() ================================================ FILE: desktop/src/client/command.ts ================================================ import { Child, ChildProcess, EventEmitter, Command as ShellCommand, } from "@tauri-apps/plugin-shell" import { debug, ErrorTypeCancelled, isError, Result, ResultError, Return, sleep } from "../lib" import { DEVPOD_BINARY, DEVPOD_FLAG_OPTION, DEVPOD_UI_ENV_VAR, DEVPOD_ADDITIONAL_ENV_VARS } from "./constants" import { TStreamEvent } from "./types" import { TAURI_SERVER_URL } from "./tauriClient" import * as log from "@tauri-apps/plugin-log" import { invoke } from "@tauri-apps/api/core" export type TStreamEventListenerFn = (event: TStreamEvent) => void export type TEventListener = Parameters< EventEmitter>["addListener"] >[1] type TStreamOptions = Readonly<{ ignoreStdoutError?: boolean ignoreStderrError?: boolean }> const defaultStreamOptions: TStreamOptions = { ignoreStdoutError: false, ignoreStderrError: false, } export type TCommand = { run(): Promise> stream(listener: TStreamEventListenerFn): Promise cancel(): Promise } export class Command implements TCommand> { private sidecarCommand: ShellCommand private childProcess?: Child private args: string[] private cancelled = false private isFlatpak: boolean | undefined private extraEnvVars: Record public static ADDITIONAL_ENV_VARS: string = "" public static HTTP_PROXY: string = "" public static HTTPS_PROXY: string = "" public static NO_PROXY: string = "" constructor(args: string[]) { debug("commands", "Creating Devpod command with args: ", args) this.extraEnvVars = Command.ADDITIONAL_ENV_VARS.split(",") .map((envVarStr) => envVarStr.split("=")) .reduce( (acc, pair) => { const [key, value] = pair if (key === undefined || value === undefined) { return acc } return { ...acc, [key]: value } }, {} as Record ) // set proxy related environment variables if (Command.HTTP_PROXY) { this.extraEnvVars["HTTP_PROXY"] = Command.HTTP_PROXY } if (Command.HTTPS_PROXY) { this.extraEnvVars["HTTPS_PROXY"] = Command.HTTPS_PROXY } if (Command.NO_PROXY) { this.extraEnvVars["NO_PROXY"] = Command.NO_PROXY } // allows the CLI to detect if commands have been invoked from the UI this.extraEnvVars[DEVPOD_UI_ENV_VAR] = "true" this.sidecarCommand = ShellCommand.sidecar(DEVPOD_BINARY, args, { env: this.extraEnvVars }) this.args = args } public async getEnv(name: string): Promise { return invoke("get_env", { name }) } public async run(): Promise>> { try { // Run once to check with the rust backend if we are running inside the flatpak sandbox // This informs the CLI wrapper to use flatpak-spawn to escape the sandbox and export this.extraEnvVars if (this.isFlatpak === undefined) { this.isFlatpak = await this.getEnv("FLATPAK_ID") if (this.isFlatpak) { this.extraEnvVars["FLATPAK_ID"] = "sh.loft.devpod" this.extraEnvVars[DEVPOD_ADDITIONAL_ENV_VARS] = recordToCSV(this.extraEnvVars) this.sidecarCommand = ShellCommand.sidecar(DEVPOD_BINARY, this.args, { env: this.extraEnvVars }) } } const rawResult = await this.sidecarCommand.execute() debug("commands", `Result for command with args ${this.args}:`, rawResult) return Return.Value(rawResult) } catch (e) { return Return.Failed(e + "") } } public async stream( listener: TStreamEventListenerFn, streamOptions?: TStreamOptions ): Promise { let opts = defaultStreamOptions if (streamOptions) { opts = { ...defaultStreamOptions, ...streamOptions } } try { this.childProcess = await this.sidecarCommand.spawn() if (this.cancelled) { await this.childProcess.kill() return Return.Failed("Command already cancelled", "", ErrorTypeCancelled) } await new Promise((res, rej) => { const stdoutListener: TEventListener<"data"> = (message) => { try { const data = JSON.parse(message) // special case: the cli sends us a message where "done" is "true" // to signal the command is terminated and we should stop listen to it // This happens for the vscode browser command as it needs to stay open // for port-forwarding, but we don't care anymore about its output. if (data?.done === "true") { res(Return.Ok()) } else { listener({ type: "data", data }) } } catch (error) { if (!opts.ignoreStdoutError) { console.error("Failed to parse stdout message ", message, error) } } } const stderrListener: TEventListener<"data"> = (message) => { try { const error = JSON.parse(message) listener({ type: "error", error }) } catch (error) { if (!opts.ignoreStderrError) { console.error("Failed to parse stderr message ", message, error) } } } this.sidecarCommand.stderr.addListener("data", stderrListener) this.sidecarCommand.stdout.addListener("data", stdoutListener) const cleanup = () => { this.sidecarCommand.stderr.removeListener("data", stderrListener) this.sidecarCommand.stdout.removeListener("data", stdoutListener) this.childProcess = undefined } this.sidecarCommand.on("close", ({ code }) => { cleanup() if (code !== 0) { rej(new Error("exit code: " + code)) } else { res(Return.Ok()) } }) this.sidecarCommand.on("error", (arg) => { cleanup() rej(arg) }) }) return Return.Ok() } catch (e) { if (isError(e)) { if (this.cancelled) { return Return.Failed(e.message, "", ErrorTypeCancelled) } return Return.Failed(e.message) } console.error(e) return Return.Failed("streaming failed") } } /** * Cancel the command. * Only works if it has been created with the `stream` method. */ public async cancel(): Promise> { try { this.cancelled = true if (!this.childProcess) { // nothing to clean up return Return.Ok() } // Try to send signal first before force killing process await fetch(TAURI_SERVER_URL + "/child-process/signal", { method: "POST", headers: { "content-type": "application/json", }, body: JSON.stringify({ processId: this.childProcess.pid, signal: 2, // SIGINT }), }) await sleep(3_000) // the actual child process could be gone after sending a SIGINT // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (this.childProcess) { await this.childProcess.kill() } return Return.Ok() } catch (e) { if (isError(e)) { return Return.Failed(e.message) } return Return.Failed("failed to cancel command") } } } export function isOk(result: ChildProcess): boolean { return result.code === 0 } export function toFlagArg(flag: string, arg: string) { return [flag, arg].join("=") } export function serializeRawOptions( rawOptions: Record, flag: string = DEVPOD_FLAG_OPTION ): string[] { return Object.entries(rawOptions).map(([key, value]) => flag + `=${key}=${value}`) } function recordToCSV(record: Record): string { return Object.entries(record) .map(([key, value]) => `${key}=${value}`) .join(','); } ================================================ FILE: desktop/src/client/commandCache.ts ================================================ import { TActionName } from "../contexts" import { exists, isEmpty, noop, ResultError, SingleEventManager, THandler } from "../lib" import { TUnsubscribeFn } from "../types" import { TCommand, TStreamEventListenerFn } from "./command" import { TStreamEvent } from "./types" export type TCommandCacheInfo = Readonly<{ id: string; actionName: TActionName }> type TCommandCacheID = `${string}:${TActionName}` type TCommandHandler = Readonly<{ promise: Promise stream?: (streamHandler?: THandler) => TUnsubscribeFn cancel?: () => Promise }> type TCommandCacheStore = Map export class CommandCache { private store: TCommandCacheStore = new Map() private getCacheID(info: TCommandCacheInfo): TCommandCacheID { return `${info.id}:${info.actionName}` } public get(info: TCommandCacheInfo): TCommandHandler | undefined { const cacheID = this.getCacheID(info) return this.store.get(cacheID) } public findCommandHandlerById(id: string) { for (const [cacheID, handler] of this.store) { const [actionID] = cacheID.split(":") if (actionID === id) { return handler } } return undefined } public clear(info: TCommandCacheInfo) { const cacheID = this.getCacheID(info) this.store.delete(cacheID) } public connect( info: TCommandCacheInfo, cmd: Readonly> ): Readonly<{ operation: TCommandHandler["promise"] stream: TCommandHandler["stream"] }> { const events: TStreamEvent[] = [] const eventManager = new SingleEventManager() const promise = cmd.stream((event) => { events.push(event) eventManager.publish(event) }) const stream: TCommandHandler["stream"] = (handler) => { if (!exists(handler)) { return noop } // events in-order before registering the new newHandler if (!isEmpty(events)) { for (const event of events) { handler.notify(event) } } // Make sure we subscribe handlers only once if (eventManager.isSubscribed(handler)) { return () => eventManager.unsubscribe(handler) } return eventManager.subscribe(handler) } const cancel: TCommandHandler["cancel"] = () => { return cmd.cancel() } const cacheID = this.getCacheID(info) this.store.set(cacheID, { promise, stream, cancel, }) return { operation: promise, stream } } } ================================================ FILE: desktop/src/client/constants.ts ================================================ export const DEVPOD_GIT_REPOSITORY = "https://github.com/loft-sh/devpod" export const DEFAULT_STATIC_COMMAND_CONFIG = { streamResponse: false, debug: false, } as const /** placeholder for arbitrary additional flags */ export const WORKSPACE_COMMAND_ADDITIONAL_FLAGS_KEY = "additionalFlags" export const DEVPOD_BINARY = "bin/devpod-cli" export const DEVPOD_COMMAND_LIST = "list" export const DEVPOD_COMMAND_STATUS = "status" export const DEVPOD_COMMAND_UP = "up" export const DEVPOD_COMMAND_STOP = "stop" export const DEVPOD_COMMAND_BUILD = "build" export const DEVPOD_COMMAND_DELETE = "delete" export const DEVPOD_COMMAND_PROVIDER = "provider" export const DEVPOD_COMMAND_IDE = "ide" export const DEVPOD_COMMAND_PRO = "pro" export const DEVPOD_COMMAND_OPTIONS = "options" export const DEVPOD_COMMAND_SET_OPTIONS = "set-options" export const DEVPOD_COMMAND_USE = "use" export const DEVPOD_COMMAND_ADD = "add" export const DEVPOD_COMMAND_HELPER = "helper" export const DEVPOD_COMMAND_UPDATE = "update" export const DEVPOD_COMMAND_CONTEXT = "context" export const DEVPOD_COMMAND_LOGIN = "login" export const DEVPOD_COMMAND_IMPORT_WORKSPACE = "import-workspace" export const DEVPOD_COMMAND_GET_WORKSPACE_NAME = "get-workspace-name" export const DEVPOD_COMMAND_GET_WORKSPACE_UID = "get-workspace-uid" export const DEVPOD_COMMAND_GET_WORKSPACE_CONFIG = "get-workspace-config" export const DEVPOD_COMMAND_GET_PROVIDER_NAME = "get-provider-name" export const DEVPOD_COMMAND_GET_PRO_NAME = "get-pro-name" export const DEVPOD_COMMAND_CHECK_PROVIDER_UPDATE = "check-provider-update" export const DEVPOD_COMMAND_TROUBLESHOOT = "troubleshoot" export const DEVPOD_FLAG_JSON_LOG_OUTPUT = "--log-output=json" export const DEVPOD_FLAG_JSON_OUTPUT = "--output=json" export const DEVPOD_FLAG_OPTION = "--option" export const DEVPOD_FLAG_FORCE = "--force" export const DEVPOD_FLAG_FORCE_BUILD = "--force-build" export const DEVPOD_FLAG_RECREATE = "--recreate" export const DEVPOD_FLAG_RESET = "--reset" export const DEVPOD_FLAG_IDE = "--ide" export const DEVPOD_FLAG_PROVIDER = "--provider" export const DEVPOD_FLAG_PROVIDER_OPTION = "--provider-option" export const DEVPOD_FLAG_ACCESS_KEY = "--access-key" export const DEVPOD_FLAG_PREBUILD_REPOSITORY = "--prebuild-repository" export const DEVPOD_FLAG_ID = "--id" export const DEVPOD_FLAG_SOURCE = "--source" export const DEVPOD_FLAG_DEBUG = "--debug" export const DEVPOD_FLAG_USE = "--use" export const DEVPOD_FLAG_NAME = "--name" export const DEVPOD_FLAG_SINGLE_MACHINE = "--single-machine" export const DEVPOD_FLAG_DRY = "--dry" export const DEVPOD_FLAG_RECONFIGURE = "--reconfigure" export const DEVPOD_FLAG_SKIP_REQUIRED = "--skip-required" export const DEVPOD_FLAG_TIMEOUT = "--timeout" export const DEVPOD_FLAG_DEVCONTAINER_PATH = "--devcontainer-path" export const DEVPOD_FLAG_WORKSPACE_ID = "--workspace-id" export const DEVPOD_FLAG_WORKSPACE_UID = "--workspace-uid" export const DEVPOD_FLAG_WORKSPACE_PROJECT = "--workspace-project" export const DEVPOD_FLAG_LOGIN = "--login" export const DEVPOD_FLAG_HOST = "--host" export const DEVPOD_FLAG_INSTANCE = "--instance" export const DEVPOD_FLAG_PROJECT = "--project" export const DEVPOD_FLAG_SKIP_PRO = "--skip-pro" export const DEVPOD_FLAG_DOTFILES = "--dotfiles" export const DEVPOD_FLAG_GIT_SIGNING_KEY = "--git-ssh-signing-key" export const DEVPOD_FLAG_FORCE_BROWSER = "--force-browser" export const DEVPOD_UI_ENV_VAR = "DEVPOD_UI" export const DEVPOD_ADDITIONAL_ENV_VARS = "DEVPOD_ADDITIONAL_ENV_VARS" ================================================ FILE: desktop/src/client/context/client.ts ================================================ import { Result, ResultError } from "../../lib" import { TContextOptionName, TContextOptions } from "../../types" import { TDebuggable } from "../types" import { ContextCommands } from "./contextCommands" export class ContextClient implements TDebuggable { constructor() {} public setDebug(isEnabled: boolean): void { ContextCommands.DEBUG = isEnabled } public async setOption(option: TContextOptionName, value: string): Promise { return ContextCommands.SetOptions({ [option]: value }) } public async listOptions(): Promise> { return ContextCommands.ListOptions() } } ================================================ FILE: desktop/src/client/context/contextCommands.ts ================================================ import { Result, ResultError, Return, getErrorFromChildProcess } from "../../lib" import { TContextOptionName, TContextOptions } from "../../types" import { Command, isOk, serializeRawOptions } from "../command" import { DEVPOD_COMMAND_CONTEXT, DEVPOD_COMMAND_OPTIONS, DEVPOD_COMMAND_SET_OPTIONS, DEVPOD_FLAG_DEBUG, DEVPOD_FLAG_JSON_LOG_OUTPUT, DEVPOD_FLAG_JSON_OUTPUT, } from "../constants" export class ContextCommands { static DEBUG = false private static newCommand(args: string[]): Command { return new Command([...args, ...(ContextCommands.DEBUG ? [DEVPOD_FLAG_DEBUG] : [])]) } static async SetOptions( rawOptions: Partial> ): Promise { const optionsFlag = serializeRawOptions(rawOptions) const result = await ContextCommands.newCommand([ DEVPOD_COMMAND_CONTEXT, DEVPOD_COMMAND_SET_OPTIONS, ...optionsFlag, DEVPOD_FLAG_JSON_LOG_OUTPUT, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Ok() } static async ListOptions(): Promise> { const result = await ContextCommands.newCommand([ DEVPOD_COMMAND_CONTEXT, DEVPOD_COMMAND_OPTIONS, DEVPOD_FLAG_JSON_OUTPUT, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } const options = JSON.parse(result.val.stdout) as TContextOptions return Return.Value(options) } } ================================================ FILE: desktop/src/client/context/index.ts ================================================ export { ContextClient } from "./client" ================================================ FILE: desktop/src/client/ides/client.ts ================================================ import { TDebuggable } from "../types" import { Result, ResultError } from "../../lib" import { TIDEs } from "../../types" import { IDECommands } from "./ideCommands" export class IDEsClient implements TDebuggable { constructor() {} public setDebug(isEnabled: boolean): void { IDECommands.DEBUG = isEnabled } public async useIDE(ide: string): Promise { return IDECommands.UseIDE(ide) } public async listAll(): Promise> { return IDECommands.ListIDEs() } } ================================================ FILE: desktop/src/client/ides/ideCommands.ts ================================================ import { Command, isOk } from "../command" import { DEVPOD_COMMAND_IDE, DEVPOD_COMMAND_LIST, DEVPOD_COMMAND_USE, DEVPOD_FLAG_DEBUG, DEVPOD_FLAG_JSON_LOG_OUTPUT, DEVPOD_FLAG_JSON_OUTPUT, } from "../constants" import { getErrorFromChildProcess, Result, ResultError, Return } from "@/lib" import { TIDEs } from "@/types" export class IDECommands { static DEBUG = false private static newCommand(args: string[]): Command { return new Command([...args, ...(IDECommands.DEBUG ? [DEVPOD_FLAG_DEBUG] : [])]) } static async UseIDE(ide: string): Promise { const result = await IDECommands.newCommand([ DEVPOD_COMMAND_IDE, DEVPOD_COMMAND_USE, ide, DEVPOD_FLAG_JSON_LOG_OUTPUT, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Ok() } static async ListIDEs(): Promise> { const result = await IDECommands.newCommand([ DEVPOD_COMMAND_IDE, DEVPOD_COMMAND_LIST, DEVPOD_FLAG_JSON_OUTPUT, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } const ides = JSON.parse(result.val.stdout) as TIDEs return Return.Value(ides) } } ================================================ FILE: desktop/src/client/ides/index.ts ================================================ export { IDEsClient } from "./client" ================================================ FILE: desktop/src/client/index.ts ================================================ export { client } from "./client" export type { TArch, TPlatform } from "./client" export { DEVPOD_GIT_REPOSITORY } from "./constants" export type { TStreamEventListenerFn } from "./types" export { ProClient } from "./pro" ================================================ FILE: desktop/src/client/pro/client.ts ================================================ import { TWorkspaceOwnerFilterState } from "@/components" import { ProWorkspaceInstance } from "@/contexts" import { DaemonStatus } from "@/gen" import { ManagementV1DevPodWorkspaceInstance } from "@loft-enterprise/client/gen/models/managementV1DevPodWorkspaceInstance" import { ManagementV1Project } from "@loft-enterprise/client/gen/models/managementV1Project" import { ManagementV1ProjectClusters } from "@loft-enterprise/client/gen/models/managementV1ProjectClusters" import { ManagementV1ProjectTemplates } from "@loft-enterprise/client/gen/models/managementV1ProjectTemplates" import { ManagementV1Self } from "@loft-enterprise/client/gen/models/managementV1Self" import { ManagementV1UserProfile } from "@loft-enterprise/client/gen/models/managementV1UserProfile" import { Result, ResultError, Return, isError, sleep } from "../../lib" import { TGitCredentialHelperData, TImportWorkspaceConfig, TListProInstancesConfig, TPlatformHealthCheck, TPlatformVersionInfo, TProID, TProInstance, } from "../../types" import { TAURI_SERVER_URL } from "../tauriClient" import { TDebuggable, TStreamEventListenerFn } from "../types" import { ProCommands } from "./proCommands" import { client as globalClient } from "@/client" export class ProClient implements TDebuggable { constructor(protected readonly id: string) {} public setDebug(isEnabled: boolean): void { ProCommands.DEBUG = isEnabled } public async login( host: string, accessKey?: string, listener?: TStreamEventListenerFn ): Promise { return ProCommands.Login(host, accessKey, listener) } public async checkHealth(): Promise> { return ProCommands.CheckHealth(this.id) } public async getVersion() { return ProCommands.GetVersion(this.id) } public async checkUpdate() { return ProCommands.CheckUpdate(this.id) } public async update(version: string) { return ProCommands.Update(this.id, version) } public async listProInstances( config?: TListProInstancesConfig ): Promise> { return ProCommands.ListProInstances(config) } public async removeProInstance(id: TProID) { return ProCommands.RemoveProInstance(id) } public async importWorkspace(config: TImportWorkspaceConfig): Promise { return ProCommands.ImportWorkspace(config) } public watchWorkspacesProxy( projectName: string, _ownerFilter: TWorkspaceOwnerFilterState, listener: (newWorkspaces: readonly ProWorkspaceInstance[]) => void ) { const cmd = ProCommands.WatchWorkspaces(this.id, projectName) // kick off stream in the background cmd.stream( (event) => { if (event.type === "data") { const rawInstances = event.data as unknown as readonly ManagementV1DevPodWorkspaceInstance[] const workspaceInstances = rawInstances.map( (instance) => new ProWorkspaceInstance(instance) ) listener(workspaceInstances) return } }, { ignoreStderrError: true } ) // Don't await here, we want to return the unsubscribe function return () => { // Still, return the promise so someone can choose to await if necessary. return cmd.cancel() } } public async listProjects(): Promise> { return ProCommands.ListProjects(this.id) } public async getSelf(): Promise> { return ProCommands.GetSelf(this.id) } public async getProjectTemplates( projectName: string ): Promise> { return ProCommands.ListTemplates(this.id, projectName) } public async getProjectClusters( projectName: string ): Promise> { return ProCommands.ListClusters(this.id, projectName) } public async createWorkspace( instance: ManagementV1DevPodWorkspaceInstance ): Promise> { return ProCommands.CreateWorkspace(this.id, instance) } public async updateWorkspace( instance: ManagementV1DevPodWorkspaceInstance ): Promise> { return ProCommands.UpdateWorkspace(this.id, instance) } } export class DaemonClient extends ProClient { constructor(id: string) { super(id) } public setDebug(isEnabled: boolean): void { ProCommands.DEBUG = isEnabled } public async login( host: string, accessKey?: string, listener?: TStreamEventListenerFn ): Promise { return ProCommands.Login(host, accessKey, listener) } private handleError(err: unknown, fallbackMsg: string): Result { if (isError(err)) { return Return.Failed(err.message) } if (typeof err === "string") { return Return.Failed(`${fallbackMsg}: ${err}`) } return Return.Failed(fallbackMsg) } private async getProxy(path: string): Promise> { try { const res = await fetch(`${TAURI_SERVER_URL}/daemon-proxy/${this.id}${path}`, { method: "GET", headers: { "content-type": "application/json", }, }) if (!res.ok) { const maybeText = await res.text() let errMessage = `Get resource: ${res.statusText}.` if (maybeText) { errMessage += maybeText } return Return.Failed(errMessage) } const json: T = await res.json() return Return.Value(json) } catch (e) { return this.handleError(e, "unable to get resource") } } private async get(path: string): Promise> { try { const res = await fetch(`${TAURI_SERVER_URL}${path}`, { method: "GET", headers: { "content-type": "application/json", }, }) if (!res.ok) { return Return.Failed(`Get resource: ${res.statusText}`) } const json: T = await res.json().catch(() => "") return Return.Value(json) } catch (e) { return this.handleError(e, "unable to get resource") } } private async post(path: string, body: BodyInit): Promise> { try { const res = await fetch(`${TAURI_SERVER_URL}/daemon-proxy/${this.id}${path}`, { method: "POST", headers: { "content-type": "application/json", }, body, }) if (!res.ok) { return Return.Failed(`Error getting resource ${path} : ${res.statusText}`) } const json: T = await res.json() return Return.Value(json) } catch (e) { return this.handleError(e, "unable to get resource") } } public async restartDaemon() { return this.get(`/daemon/${this.id}/restart`) } public async checkHealth(): Promise> { // NOTE: We don't access this through the proxy because there might be issues during daemon startup // that we couldn't surface otherwise const res = await this.get(`/daemon/${this.id}/status`) if (!res.ok) { return res } const status = res.val let healthy = status.state === "running" const details = [] if (status.loginRequired) { healthy = false details.push("Login required to connect to platform") } if (status.state === "pending") { details.push("Daemon is starting up") } // if the backend state is running but we are not online, something is wrong with networking if (!status.online) { healthy = false details.push("Platform is offline") } return Return.Value({ healthy, details, loginRequired: status.loginRequired, online: status.online }) } public watchWorkspaces( projectName: string, ownerFilter: TWorkspaceOwnerFilterState, listener: TWorksaceListener ): () => void { const watcher = new WorkspaceWatcher(this.id, projectName, ownerFilter, listener) return watcher.watch() } public async getSelf(): Promise> { return this.getProxy("/self") } public async getUserProfile(): Promise> { return this.getProxy("/user-profile") } public async updateUserProfile( userProfile: ManagementV1UserProfile ): Promise> { try { const body = JSON.stringify(userProfile) const res = (await this.post("/update-user-profile", body)) as Result return res } catch (e) { return this.handleError(e, "failed to update workspace") } } public async listProjects(): Promise> { return this.getProxy("/projects") } public async getVersion() { return this.getProxy("/version") } public async getProjectTemplates( projectName: string ): Promise> { return this.getProxy(`/projects/${projectName}/templates`) } public async getProjectClusters( projectName: string ): Promise> { return this.getProxy(`/projects/${projectName}/clusters`) } public async createWorkspace( instance: ManagementV1DevPodWorkspaceInstance ): Promise> { try { const body = JSON.stringify(instance) return this.post("/create-workspace", body) } catch (e) { return this.handleError(e, "failed to create workspace") } } public async updateWorkspace( instance: ManagementV1DevPodWorkspaceInstance ): Promise> { try { const body = JSON.stringify(instance) return this.post("/update-workspace", body) } catch (e) { return this.handleError(e, "failed to update workspace") } } public async queryGitCredentialsHelper( host: string ): Promise> { const searchParams = new URLSearchParams([["host", host]]) return this.getProxy("/git-credentials?" + searchParams.toString()) } public async checkUpdate() { return Return.Failed("provider is built-in, update is not supported") } // eslint-disable-next-line @typescript-eslint/no-unused-vars public async update(_version: string) { return Return.Failed("provider is built-in, update is not supported") } } type TWorksaceListener = (newWorkspaces: readonly ProWorkspaceInstance[]) => void class WorkspaceWatcher { private abortController = new AbortController() private reader: ReadableStreamDefaultReader | undefined private buffer: string = "" constructor( private readonly hostID: string, private readonly projectName: string, private readonly ownerFilter: TWorkspaceOwnerFilterState, private readonly listener: TWorksaceListener ) {} public cancel() { try { this.abortController.abort("watcher cancelled") this.reader?.cancel().catch((err) => { console.debug("cancel failed", err) }) } catch(err) { console.error(err) } this.reader = undefined this.buffer = "" } public watch(): () => void { try { const url = new URL(`${TAURI_SERVER_URL}/daemon-proxy/${this.hostID}/watch-workspaces`) url.searchParams.set("project", this.projectName) url.searchParams.set("owner", this.ownerFilter) // start long-lived request. This should never stop unless cancelled through abortController fetch(url, { method: "GET", headers: { "content-type": "application/json" }, keepalive: true, signal: this.abortController.signal, }) .then((res) => { this.reader = res.body?.getReader() return this.read() }) .catch((err) => { globalClient.log("info", `[${this.hostID}] watch workspaces error: ${err}`) }) .finally(async () => { if (!this.abortController.signal.aborted && !(await this.reader?.closed)) { // Either the webview or the daemon terminated the watcher, try to reconnect console.info("reconnect") this.reader = undefined this.buffer = "" this.watch() } // Otherwise caller is responsible for reestablishing connection }) return this.cancel.bind(this) } catch { return this.cancel.bind(this) } } private async read(): Promise { const decoder = new TextDecoder() try { if (!this.reader) { return } const { done, value } = await this.reader.read() if (done) { return } this.buffer += decoder.decode(value, { stream: true }) // NOTE: This relies on sender to end every message with a newline character. Make sure you also update the daemon server if you change this! const lines = this.buffer.split("\n") // Keep the last partial line in the buffer const maybeLine = lines.pop() if (maybeLine !== undefined) { this.buffer = maybeLine } lines.forEach((line) => { if (line.trim()) { try { const rawInstances: readonly ManagementV1DevPodWorkspaceInstance[] = JSON.parse(line) const workspaceInstances = rawInstances.map( (instance) => new ProWorkspaceInstance(instance) ) this.listener(workspaceInstances) } catch (err) { const res = this.handleError(err, "failed to parse workspaces") if (res.err) { return err } } } }) // Continue reading this.read() } catch (err) { return err } } private handleError(err: unknown, fallbackMsg: string): Result { if (isError(err)) { return Return.Failed(err.message) } if (typeof err === "string") { return Return.Failed(`${fallbackMsg}: ${err}`) } return Return.Failed(fallbackMsg) } } ================================================ FILE: desktop/src/client/pro/index.ts ================================================ export { ProClient } from "./client" ================================================ FILE: desktop/src/client/pro/proCommands.ts ================================================ import { Result, ResultError, Return, getErrorFromChildProcess } from "@/lib" import { TImportWorkspaceConfig, TListProInstancesConfig, TPlatformHealthCheck, TProID, TProInstance, TPlatformVersionInfo, TPlatformUpdateCheck, } from "@/types" import { Command, isOk, serializeRawOptions, toFlagArg } from "../command" import { DEVPOD_COMMAND_DELETE, DEVPOD_COMMAND_IMPORT_WORKSPACE, DEVPOD_COMMAND_LIST, DEVPOD_COMMAND_LOGIN, DEVPOD_COMMAND_PRO, DEVPOD_FLAG_ACCESS_KEY, DEVPOD_FLAG_DEBUG, DEVPOD_FLAG_FORCE_BROWSER, DEVPOD_FLAG_HOST, DEVPOD_FLAG_INSTANCE, DEVPOD_FLAG_JSON_LOG_OUTPUT, DEVPOD_FLAG_JSON_OUTPUT, DEVPOD_FLAG_LOGIN, DEVPOD_FLAG_PROJECT, DEVPOD_FLAG_USE, DEVPOD_FLAG_WORKSPACE_ID, DEVPOD_FLAG_WORKSPACE_PROJECT, DEVPOD_FLAG_WORKSPACE_UID, } from "../constants" import { TStreamEventListenerFn } from "../types" import { ManagementV1DevPodWorkspaceInstance } from "@loft-enterprise/client/gen/models/managementV1DevPodWorkspaceInstance" import { ManagementV1Project } from "@loft-enterprise/client/gen/models/managementV1Project" import { ManagementV1Self } from "@loft-enterprise/client/gen/models/managementV1Self" import { ManagementV1ProjectTemplates } from "@loft-enterprise/client/gen/models/managementV1ProjectTemplates" import { ManagementV1ProjectClusters } from "@loft-enterprise/client/gen/models/managementV1ProjectClusters" export class ProCommands { static DEBUG = false private static newCommand(args: string[]): Command { return new Command([...args, ...(ProCommands.DEBUG ? [DEVPOD_FLAG_DEBUG] : [])]) } static async Login( host: string, accessKey?: string, listener?: TStreamEventListenerFn ): Promise { const maybeAccessKeyFlag = accessKey ? [toFlagArg(DEVPOD_FLAG_ACCESS_KEY, accessKey)] : [] const useFlag = toFlagArg(DEVPOD_FLAG_USE, "false") const cmd = ProCommands.newCommand([ DEVPOD_COMMAND_PRO, DEVPOD_COMMAND_LOGIN, host, useFlag, DEVPOD_FLAG_FORCE_BROWSER, DEVPOD_FLAG_JSON_LOG_OUTPUT, ...maybeAccessKeyFlag, ]) if (listener) { return cmd.stream(listener) } else { const result = await cmd.run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Ok() } } static async ListProInstances( config?: TListProInstancesConfig ): Promise> { const maybeLoginFlag = config?.authenticate ? [DEVPOD_FLAG_LOGIN] : [] const result = await ProCommands.newCommand([ DEVPOD_COMMAND_PRO, DEVPOD_COMMAND_LIST, DEVPOD_FLAG_JSON_OUTPUT, ...maybeLoginFlag, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } const instances = JSON.parse(result.val.stdout) as readonly TProInstance[] return Return.Value(instances) } static async RemoveProInstance(id: TProID) { const result = await ProCommands.newCommand([ DEVPOD_COMMAND_PRO, DEVPOD_COMMAND_DELETE, id, DEVPOD_FLAG_JSON_LOG_OUTPUT, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Ok() } static async ImportWorkspace(config: TImportWorkspaceConfig): Promise { const optionsFlag = config.options ? serializeRawOptions(config.options) : [] const result = await new Command([ DEVPOD_COMMAND_PRO, DEVPOD_COMMAND_IMPORT_WORKSPACE, config.devPodProHost, DEVPOD_FLAG_WORKSPACE_ID, config.workspaceID, DEVPOD_FLAG_WORKSPACE_UID, config.workspaceUID, DEVPOD_FLAG_WORKSPACE_PROJECT, config.project, ...optionsFlag, DEVPOD_FLAG_JSON_LOG_OUTPUT, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Ok() } static WatchWorkspaces(id: TProID, projectName: string) { const hostFlag = toFlagArg(DEVPOD_FLAG_HOST, id) const projectFlag = toFlagArg(DEVPOD_FLAG_PROJECT, projectName) const args = [DEVPOD_COMMAND_PRO, "watch-workspaces", hostFlag, projectFlag] return ProCommands.newCommand(args) } static async ListProjects(id: TProID) { const hostFlag = toFlagArg(DEVPOD_FLAG_HOST, id) const args = [DEVPOD_COMMAND_PRO, "list-projects", hostFlag] const result = await ProCommands.newCommand(args).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Value(JSON.parse(result.val.stdout) as readonly ManagementV1Project[]) } static async GetSelf(id: TProID) { const hostFlag = toFlagArg(DEVPOD_FLAG_HOST, id) const args = [DEVPOD_COMMAND_PRO, "self", hostFlag] const result = await ProCommands.newCommand(args).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Value(JSON.parse(result.val.stdout) as ManagementV1Self) } static async ListTemplates(id: TProID, projectName: string) { const hostFlag = toFlagArg(DEVPOD_FLAG_HOST, id) const projectFlag = toFlagArg(DEVPOD_FLAG_PROJECT, projectName) const args = [DEVPOD_COMMAND_PRO, "list-templates", hostFlag, projectFlag] const result = await ProCommands.newCommand(args).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Value(JSON.parse(result.val.stdout) as ManagementV1ProjectTemplates) } static async ListClusters(id: TProID, projectName: string) { const hostFlag = toFlagArg(DEVPOD_FLAG_HOST, id) const projectFlag = toFlagArg(DEVPOD_FLAG_PROJECT, projectName) const args = [DEVPOD_COMMAND_PRO, "list-clusters", hostFlag, projectFlag] const result = await ProCommands.newCommand(args).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Value(JSON.parse(result.val.stdout) as ManagementV1ProjectClusters) } static async CreateWorkspace(id: TProID, instance: ManagementV1DevPodWorkspaceInstance) { const hostFlag = toFlagArg(DEVPOD_FLAG_HOST, id) const instanceFlag = toFlagArg(DEVPOD_FLAG_INSTANCE, JSON.stringify(instance)) const args = [DEVPOD_COMMAND_PRO, "create-workspace", hostFlag, instanceFlag] const result = await ProCommands.newCommand(args).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Value(JSON.parse(result.val.stdout) as ManagementV1DevPodWorkspaceInstance) } static async UpdateWorkspace(id: TProID, instance: ManagementV1DevPodWorkspaceInstance) { const hostFlag = toFlagArg(DEVPOD_FLAG_HOST, id) const instanceFlag = toFlagArg(DEVPOD_FLAG_INSTANCE, JSON.stringify(instance)) const args = [DEVPOD_COMMAND_PRO, "update-workspace", hostFlag, instanceFlag] const result = await ProCommands.newCommand(args).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Value(JSON.parse(result.val.stdout) as ManagementV1DevPodWorkspaceInstance) } static async CheckHealth(id: TProID) { const hostFlag = toFlagArg(DEVPOD_FLAG_HOST, id) const args = [DEVPOD_COMMAND_PRO, "check-health", hostFlag] const result = await ProCommands.newCommand(args).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Value(JSON.parse(result.val.stdout) as TPlatformHealthCheck) } static async GetVersion(id: TProID) { const hostFlag = toFlagArg(DEVPOD_FLAG_HOST, id) const args = [DEVPOD_COMMAND_PRO, "version", hostFlag] const result = await ProCommands.newCommand(args).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Value(JSON.parse(result.val.stdout) as TPlatformVersionInfo) } static async CheckUpdate(id: TProID) { const hostFlag = toFlagArg(DEVPOD_FLAG_HOST, id) const args = [DEVPOD_COMMAND_PRO, "check-update", hostFlag] const result = await ProCommands.newCommand(args).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Value(JSON.parse(result.val.stdout) as TPlatformUpdateCheck) } static async Update(id: TProID, version: string) { const hostFlag = toFlagArg(DEVPOD_FLAG_HOST, id) const args = [DEVPOD_COMMAND_PRO, "update-provider", version, hostFlag] const result = await ProCommands.newCommand(args).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Value(JSON.parse(result.val.stdout) as TPlatformUpdateCheck) } } ================================================ FILE: desktop/src/client/providers/client.ts ================================================ import { FileStorageBackend, Result, ResultError, Return, Store, isEmpty } from "../../lib" import { TAddProviderConfig, TCheckProviderUpdateResult, TConfigureProviderConfig, TProviderID, TProviderOptions, TProviderSource, TProviders, } from "../../types" import { TDebuggable } from "../types" import { ProviderCommands } from "./providerCommands" // WARN: These need to match the rust `file_name` and `dangling_provider_key` constants // for reliable cleanup! // Make sure to update them in `src/provider.rs` if you change them here! const PROVIDERS_STORE_FILE_NAME = "providers" const PROVIDERS_STORE_DANGLING_PROVIDER_KEY = "danglingProviders" type TProviderStore = Readonly<{ [PROVIDERS_STORE_DANGLING_PROVIDER_KEY]: readonly TProviderID[] }> export class ProvidersClient implements TDebuggable { private readonly store = new Store( new FileStorageBackend(PROVIDERS_STORE_FILE_NAME) ) private danglingProviderIDs: TProviderID[] = [] // Queues store operations and guarantees they will be executed in order private storeOperationQueue: Promise = Promise.resolve() constructor() {} public setDebug(isEnabled: boolean): void { ProviderCommands.DEBUG = isEnabled } public async listAll(): Promise> { return ProviderCommands.ListProviders() } public async newID(rawSource: string): Promise> { return ProviderCommands.GetProviderID(rawSource) } public async checkUpdate(id: TProviderID): Promise> { return ProviderCommands.CheckProviderUpdate(id) } public async update(id: TProviderID, source: TProviderSource): Promise> { return ProviderCommands.UpdateProvider(id, source) } public async add(rawSource: TProviderID, config: TAddProviderConfig): Promise { return ProviderCommands.AddProvider(rawSource, config) } public async remove(id: TProviderID): Promise { return ProviderCommands.RemoveProvider(id) } public async getOptions(id: TProviderID): Promise> { return ProviderCommands.GetProviderOptions(id) } public async useProvider(id: TProviderID): Promise { return ProviderCommands.UseProvider(id) } public async setOptionsDry( id: TProviderID, { options, reconfigure }: TConfigureProviderConfig ): Promise> { return ProviderCommands.SetProviderOptions(id, options, false, true, reconfigure) } public async configure( id: TProviderID, { useAsDefaultProvider, reuseMachine, options }: TConfigureProviderConfig ): Promise { const setResult = await ProviderCommands.SetProviderOptions(id, options, !!reuseMachine) if (setResult.err) { return setResult as ResultError } if (useAsDefaultProvider) { return ProviderCommands.UseProvider(id) } return Return.Ok() } public setDangling(id: TProviderID): void { this.danglingProviderIDs.push(id) const ids = this.danglingProviderIDs.slice() this.storeOperationQueue = this.storeOperationQueue.then(() => this.store.set("danglingProviders", ids) ) } public popAllDangling(): readonly TProviderID[] { const maybeProviderIDs = this.danglingProviderIDs.slice() this.danglingProviderIDs.length = 0 this.storeOperationQueue = this.storeOperationQueue.then(() => this.store.remove("danglingProviders") ) return maybeProviderIDs } public popDangling(): TProviderID | undefined { const lastProviderID = this.danglingProviderIDs.pop() const ids = this.danglingProviderIDs.slice() this.storeOperationQueue = this.storeOperationQueue.then(() => { if (isEmpty(ids)) { return this.store.remove("danglingProviders") } return this.store.set("danglingProviders", ids) }) return lastProviderID } } ================================================ FILE: desktop/src/client/providers/index.ts ================================================ export { ProvidersClient } from "./client" ================================================ FILE: desktop/src/client/providers/providerCommands.ts ================================================ import { exists, getErrorFromChildProcess, Result, ResultError, Return } from "../../lib" import { TAddProviderConfig, TCheckProviderUpdateResult, TProviderID, TProviderOptions, TProviders, TProviderSource, } from "../../types" import { Command, isOk, serializeRawOptions, toFlagArg } from "../command" import { DEVPOD_COMMAND_ADD, DEVPOD_COMMAND_DELETE, DEVPOD_COMMAND_GET_PROVIDER_NAME, DEVPOD_COMMAND_LIST, DEVPOD_COMMAND_OPTIONS, DEVPOD_COMMAND_PROVIDER, DEVPOD_COMMAND_SET_OPTIONS, DEVPOD_COMMAND_UPDATE, DEVPOD_COMMAND_USE, DEVPOD_FLAG_DEBUG, DEVPOD_FLAG_DRY, DEVPOD_FLAG_JSON_LOG_OUTPUT, DEVPOD_FLAG_JSON_OUTPUT, DEVPOD_FLAG_NAME, DEVPOD_FLAG_RECONFIGURE, DEVPOD_FLAG_SINGLE_MACHINE, DEVPOD_FLAG_USE, } from "../constants" import { DEVPOD_COMMAND_CHECK_PROVIDER_UPDATE, DEVPOD_COMMAND_HELPER } from "./../constants" export class ProviderCommands { static DEBUG = false private static newCommand(args: string[]): Command { return new Command([...args, ...(ProviderCommands.DEBUG ? [DEVPOD_FLAG_DEBUG] : [])]) } static async ListProviders(): Promise> { const result = await new Command([ DEVPOD_COMMAND_PROVIDER, DEVPOD_COMMAND_LIST, DEVPOD_FLAG_JSON_OUTPUT, DEVPOD_FLAG_JSON_LOG_OUTPUT, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } const rawProviders = JSON.parse(result.val.stdout) as TProviders for (const provider of Object.values(rawProviders)) { provider.isProxyProvider = provider.config?.exec?.proxy !== undefined || provider.config?.exec?.daemon !== undefined } return Return.Value(rawProviders) } static async GetProviderID(source: string) { const result = await new Command([ DEVPOD_COMMAND_HELPER, DEVPOD_COMMAND_GET_PROVIDER_NAME, source, DEVPOD_FLAG_JSON_LOG_OUTPUT, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Value(result.val.stdout) } static async AddProvider( rawProviderSource: string, config: TAddProviderConfig ): Promise { const maybeName = config.name const maybeNameFlag = exists(maybeName) ? [toFlagArg(DEVPOD_FLAG_NAME, maybeName)] : [] const useFlag = toFlagArg(DEVPOD_FLAG_USE, "false") const result = await ProviderCommands.newCommand([ DEVPOD_COMMAND_PROVIDER, DEVPOD_COMMAND_ADD, rawProviderSource, ...maybeNameFlag, useFlag, DEVPOD_FLAG_JSON_LOG_OUTPUT, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Ok() } static async RemoveProvider(id: TProviderID) { const result = await ProviderCommands.newCommand([ DEVPOD_COMMAND_PROVIDER, DEVPOD_COMMAND_DELETE, id, DEVPOD_FLAG_JSON_LOG_OUTPUT, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Ok() } static async UseProvider( id: TProviderID, rawOptions?: Record, reuseMachine?: boolean ) { const optionsFlag = rawOptions ? serializeRawOptions(rawOptions) : [] const maybeResuseMachineFlag = reuseMachine ? [DEVPOD_FLAG_SINGLE_MACHINE] : [] const result = await ProviderCommands.newCommand([ DEVPOD_COMMAND_PROVIDER, DEVPOD_COMMAND_USE, id, ...optionsFlag, ...maybeResuseMachineFlag, DEVPOD_FLAG_JSON_LOG_OUTPUT, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Ok() } static async SetProviderOptions( id: TProviderID, rawOptions: Record, reuseMachine: boolean, dry?: boolean, reconfigure?: boolean ) { const optionsFlag = serializeRawOptions(rawOptions) const maybeResuseMachineFlag = reuseMachine ? [DEVPOD_FLAG_SINGLE_MACHINE] : [] const maybeDry = dry ? [DEVPOD_FLAG_DRY] : [] const maybeReconfigure = reconfigure ? [DEVPOD_FLAG_RECONFIGURE] : [] const result = await ProviderCommands.newCommand([ DEVPOD_COMMAND_PROVIDER, DEVPOD_COMMAND_SET_OPTIONS, id, ...optionsFlag, ...maybeResuseMachineFlag, ...maybeDry, ...maybeReconfigure, DEVPOD_FLAG_JSON_LOG_OUTPUT, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } else if (dry) { return Return.Value(JSON.parse(result.val.stdout) as TProviderOptions) } return Return.Ok() } static async GetProviderOptions(id: TProviderID) { const result = await new Command([ DEVPOD_COMMAND_PROVIDER, DEVPOD_COMMAND_OPTIONS, id, DEVPOD_FLAG_JSON_OUTPUT, DEVPOD_FLAG_JSON_LOG_OUTPUT, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Value(JSON.parse(result.val.stdout) as TProviderOptions) } static async CheckProviderUpdate(id: TProviderID) { const result = await new Command([ DEVPOD_COMMAND_HELPER, DEVPOD_COMMAND_CHECK_PROVIDER_UPDATE, id, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Value(JSON.parse(result.val.stdout) as TCheckProviderUpdateResult) } static async UpdateProvider(id: TProviderID, source: TProviderSource) { const useFlag = toFlagArg(DEVPOD_FLAG_USE, "false") const result = await new Command([ DEVPOD_COMMAND_PROVIDER, DEVPOD_COMMAND_UPDATE, id, source.raw ?? source.github ?? source.url ?? source.file ?? "", DEVPOD_FLAG_JSON_LOG_OUTPUT, useFlag, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return getErrorFromChildProcess(result.val) } return Return.Ok() } } ================================================ FILE: desktop/src/client/tauriClient/index.ts ================================================ export const TAURI_SERVER_URL = "http://localhost:25842" ================================================ FILE: desktop/src/client/types.ts ================================================ import { TLogOutput } from "../types" export type TDebuggable = Readonly<{ setDebug(isEnabled: boolean): void }> export type TStreamEvent = Readonly< { type: "data"; data: TLogOutput } | { type: "error"; error: TLogOutput } > export type TStreamEventListenerFn = (event: TStreamEvent) => void ================================================ FILE: desktop/src/client/workspaces/client.ts ================================================ import { TActionID, TActionName, TActionObj } from "../../contexts" import { Result, ResultError, Return, THandler, exists, isError, noop } from "../../lib" import { TDevcontainerSetup, TStreamID, TUnsubscribeFn, TWorkspace, TWorkspaceID, TWorkspaceStartConfig, TWorkspaceWithoutStatus, } from "../../types" import { TCommand, TStreamEventListenerFn } from "../command" import { CommandCache, TCommandCacheInfo } from "../commandCache" import { TDebuggable, TStreamEvent } from "../types" import { WorkspaceCommands } from "./workspaceCommands" import { DEVPOD_FLAG_DOTFILES, DEVPOD_FLAG_GIT_SIGNING_KEY, WORKSPACE_COMMAND_ADDITIONAL_FLAGS_KEY, } from "../constants" import { invoke } from "@tauri-apps/api/core" // Every workspace can have one active action at a time, // but multiple views might need to listen to the same action. // The `streamID` identifies a view listener. type TWorkspaceClientContext = Readonly<{ id: TWorkspaceID actionID: TActionID streamID: TStreamID }> export class WorkspacesClient implements TDebuggable { private readonly commandCache = new CommandCache() constructor() {} private createStreamHandler( id: TStreamID, listener: TStreamEventListenerFn ): THandler { return { id, eq(other) { return id === other.id }, notify: listener, } } private async writeEvent(actionID: TActionID, event: TStreamEvent) { // Be wary of the spelling, tauri expects this to be `actionId` instead of `actionID` because of the serde deserialization await invoke("write_action_log", { actionId: actionID, data: JSON.stringify(event) }) } private async execActionCmd( cmd: Readonly>, ctx: Readonly<{ id: TWorkspaceID actionID: TActionID streamID: TStreamID listener?: TStreamEventListenerFn | undefined actionName: TActionName }> ) { const cacheInfo: TCommandCacheInfo = { id: ctx.id, actionName: ctx.actionName } const maybeRunningCommand = this.commandCache.get(cacheInfo) const handler = this.createStreamHandler(ctx.streamID, (event) => { this.writeEvent(ctx.actionID, event) ctx.listener?.(event) }) // If `start` for id is running already, // wire up the new listener and return the existing operation if (exists(maybeRunningCommand)) { maybeRunningCommand.stream?.(handler) await maybeRunningCommand.promise return this.getStatus(ctx.id) } const { operation, stream } = this.commandCache.connect(cacheInfo, cmd) stream?.(handler) const result = await operation this.commandCache.clear(cacheInfo) if (result.err) { return result } return result } public setDebug(isEnabled: boolean): void { WorkspaceCommands.DEBUG = isEnabled } public setDotfilesFlag(dotfilesUrl: string): void { if (!dotfilesUrl) { return } WorkspaceCommands.ADDITIONAL_FLAGS.set(DEVPOD_FLAG_DOTFILES, dotfilesUrl) } public setAdditionalFlags(additionalFlags: string): void { WorkspaceCommands.ADDITIONAL_FLAGS.set(WORKSPACE_COMMAND_ADDITIONAL_FLAGS_KEY, additionalFlags) } public setSSHKeyPath(sshKeyPath: string): void { if (!sshKeyPath) { return } WorkspaceCommands.ADDITIONAL_FLAGS.set(DEVPOD_FLAG_GIT_SIGNING_KEY, sshKeyPath) } public async listAll( skipPro: boolean = true ): Promise> { return WorkspaceCommands.ListWorkspaces(skipPro) } public async getStatus(id: TWorkspaceID): Promise> { const result = await WorkspaceCommands.FetchWorkspaceStatus(id) if (result.err) { return result } const { status } = result.val return Return.Value(status) } public async newID(rawSource: string): Promise> { return WorkspaceCommands.GetWorkspaceID(rawSource) } public async newUID(): Promise> { return WorkspaceCommands.GetWorkspaceUID() } public async start( config: TWorkspaceStartConfig, listener: TStreamEventListenerFn | undefined, ctx: TWorkspaceClientContext ): Promise> { const cmd = WorkspaceCommands.StartWorkspace(ctx.id, config) const result = await this.execActionCmd(cmd, { ...ctx, listener, actionName: "start" }) if (result.err) { return result } return this.getStatus(ctx.id) } public async stop( listener: TStreamEventListenerFn | undefined, ctx: TWorkspaceClientContext ): Promise> { const cmd = WorkspaceCommands.StopWorkspace(ctx.id) const result = await this.execActionCmd(cmd, { ...ctx, listener, actionName: "stop" }) if (result.err) { return result } return this.getStatus(ctx.id) } public async rebuild( listener: TStreamEventListenerFn | undefined, ctx: TWorkspaceClientContext ): Promise> { const cmd = WorkspaceCommands.RebuildWorkspace(ctx.id) const result = await this.execActionCmd(cmd, { ...ctx, listener, actionName: "rebuild" }) if (result.err) { return result } return this.getStatus(ctx.id) } public async troubleshoot(ctx: TWorkspaceClientContext) { const cmd = WorkspaceCommands.TroubleshootWorkspace(ctx.id) return cmd.run() } public async reset( listener: TStreamEventListenerFn | undefined, ctx: TWorkspaceClientContext ): Promise> { const cmd = WorkspaceCommands.ResetWorkspace(ctx.id) const result = await this.execActionCmd(cmd, { ...ctx, listener, actionName: "reset" }) if (result.err) { return result } return this.getStatus(ctx.id) } public async remove( force: boolean, listener: TStreamEventListenerFn | undefined, ctx: TWorkspaceClientContext ): Promise> { const cmd = WorkspaceCommands.RemoveWorkspace(ctx.id, force) const result = await this.execActionCmd(cmd, { ...ctx, listener, actionName: "remove" }) if (result.err) { return result } return result } public async checkStatus( listener: TStreamEventListenerFn | undefined, ctx: TWorkspaceClientContext ): Promise { const cmd = WorkspaceCommands.GetStatusLogs(ctx.id) const result = await this.execActionCmd(cmd, { ...ctx, listener, actionName: "checkStatus" }) if (result.err) { return result } return Return.Ok() } public async checkDevcontainerSetup(rawSource: string): Promise> { const result = await WorkspaceCommands.GetDevcontainerConfig(rawSource).run() if (result.err) { return result } try { const setup = JSON.parse(result.val.stdout) as TDevcontainerSetup return Return.Value(setup) } catch (err) { return Return.Failed(`Failed to parse devcontainer setup: ${err}`) } } public subscribe( action: TActionObj, streamID: TStreamID, listener: TStreamEventListenerFn ): TUnsubscribeFn { const maybeRunningCommand = this.commandCache.get({ id: action.targetID, actionName: action.name, }) if (!exists(maybeRunningCommand)) { return noop } const maybeUnsubscribe = maybeRunningCommand.stream?.( this.createStreamHandler(streamID, listener) ) return () => maybeUnsubscribe?.() } public replayAction(actionID: TActionID, listener: TStreamEventListenerFn): TUnsubscribeFn { let cancelled = false const unsubscribe = () => { cancelled = true } // Be wary of the spelling, tauri expects this to be `actionId` instead of `actionID` because of the serde deserialization invoke("get_action_logs", { actionId: actionID }) .then((events) => { if (cancelled) { return } for (const event of events) { try { listener(JSON.parse(event)) } catch (e) { console.log(e) // noop } } }) .catch((e) => { console.error("Failed to replay action", e) unsubscribe() }) return unsubscribe } public async cancelAction(actionID: TActionID): Promise { const cmdHandler = this.commandCache.findCommandHandlerById(actionID) return cmdHandler?.cancel?.() ?? Return.Ok() } public async getActionLogFile(actionID: TActionID): Promise> { try { const path = await invoke("get_action_log_file", { actionId: actionID }) return Return.Value(path) } catch (e) { if (isError(e)) { return Return.Failed(e.message) } return Return.Failed(`Unable to retrieve log file for action ${actionID}`) } } } ================================================ FILE: desktop/src/client/workspaces/index.ts ================================================ export { WorkspacesClient } from "./client" ================================================ FILE: desktop/src/client/workspaces/workspaceCommands.ts ================================================ import { exists, Result, Return } from "../../lib" import { TWorkspace, TWorkspaceID, TWorkspaceStartConfig, TWorkspaceStatusResult, TWorkspaceWithoutStatus, } from "../../types" import { Command, isOk, serializeRawOptions, toFlagArg } from "../command" import { DEVPOD_COMMAND_DELETE, DEVPOD_COMMAND_GET_WORKSPACE_CONFIG, DEVPOD_COMMAND_GET_WORKSPACE_NAME, DEVPOD_COMMAND_GET_WORKSPACE_UID, DEVPOD_COMMAND_HELPER, DEVPOD_COMMAND_LIST, DEVPOD_COMMAND_STATUS, DEVPOD_COMMAND_STOP, DEVPOD_COMMAND_UP, DEVPOD_COMMAND_TROUBLESHOOT, DEVPOD_FLAG_DEBUG, DEVPOD_FLAG_DEVCONTAINER_PATH, DEVPOD_FLAG_FORCE, DEVPOD_FLAG_ID, DEVPOD_FLAG_IDE, DEVPOD_FLAG_JSON_LOG_OUTPUT, DEVPOD_FLAG_JSON_OUTPUT, DEVPOD_FLAG_PREBUILD_REPOSITORY, DEVPOD_FLAG_PROVIDER, DEVPOD_FLAG_PROVIDER_OPTION, DEVPOD_FLAG_RECREATE, DEVPOD_FLAG_RESET, DEVPOD_FLAG_SKIP_PRO, DEVPOD_FLAG_SOURCE, DEVPOD_FLAG_TIMEOUT, WORKSPACE_COMMAND_ADDITIONAL_FLAGS_KEY, } from "../constants" type TRawWorkspaces = readonly (Omit & Readonly<{ id: string | null }>)[] export class WorkspaceCommands { static DEBUG = false static ADDITIONAL_FLAGS = new Map() private static newCommand(args: string[]): Command { const extraFlags = [] if (WorkspaceCommands.DEBUG) { extraFlags.push(DEVPOD_FLAG_DEBUG) } return new Command([...args, ...extraFlags]) } static async ListWorkspaces(skipPro: boolean): Promise> { const maybeSkipProFlag = skipPro ? [DEVPOD_FLAG_SKIP_PRO] : [] const result = await new Command([ DEVPOD_COMMAND_LIST, DEVPOD_FLAG_JSON_OUTPUT, ...maybeSkipProFlag, ]).run() if (result.err) { return result } const rawWorkspaces = JSON.parse(result.val.stdout) as TRawWorkspaces return Return.Value( rawWorkspaces.filter((workspace): workspace is TWorkspaceWithoutStatus => exists(workspace.id) ) ) } static async FetchWorkspaceStatus( id: string ): Promise>> { const result = await new Command([DEVPOD_COMMAND_STATUS, id, DEVPOD_FLAG_JSON_OUTPUT]).run() if (result.err) { return result } if (!isOk(result.val)) { return Return.Failed(`Failed to get status for workspace ${id}: ${result.val.stderr}`) } const { state } = JSON.parse(result.val.stdout) as TWorkspaceStatusResult return Return.Value({ id, status: state }) } static async GetWorkspaceID(source: string) { const result = await new Command([ DEVPOD_COMMAND_HELPER, DEVPOD_COMMAND_GET_WORKSPACE_NAME, source, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return Return.Failed(`Failed to get ID for workspace source ${source}: ${result.val.stderr}`) } return Return.Value(result.val.stdout) } static async GetWorkspaceUID() { const result = await new Command([ DEVPOD_COMMAND_HELPER, DEVPOD_COMMAND_GET_WORKSPACE_UID, ]).run() if (result.err) { return result } if (!isOk(result.val)) { return Return.Failed(`Failed to get UID: ${result.val.stderr}`) } return Return.Value(result.val.stdout) } static GetStatusLogs(id: string) { return new Command([DEVPOD_COMMAND_STATUS, id, DEVPOD_FLAG_JSON_LOG_OUTPUT]) } static StartWorkspace(id: TWorkspaceID, config: TWorkspaceStartConfig) { const maybeSource = config.sourceConfig?.source const maybeIDFlag = exists(maybeSource) ? [toFlagArg(DEVPOD_FLAG_ID, id)] : [] const maybeSourceType = config.sourceConfig?.type const maybeSourceFlag = exists(maybeSourceType) && exists(maybeSource) ? [toFlagArg(DEVPOD_FLAG_SOURCE, `${maybeSourceType}:${maybeSource}`)] : [] const identifier = exists(maybeSource) && exists(maybeIDFlag) ? maybeSource : id const maybeIdeName = config.ideConfig?.name const maybeIDEFlag = exists(maybeIdeName) ? [toFlagArg(DEVPOD_FLAG_IDE, maybeIdeName)] : [] const maybeProviderID = config.providerConfig?.providerID const maybeProviderFlag = exists(maybeProviderID) ? [toFlagArg(DEVPOD_FLAG_PROVIDER, maybeProviderID)] : [] const maybeProviderOptions = config.providerConfig?.options const maybeProviderOptionsFlag = exists(maybeProviderOptions) ? serializeRawOptions(maybeProviderOptions, DEVPOD_FLAG_PROVIDER_OPTION) : [] const maybePrebuildRepositories = config.prebuildRepositories?.length ? [toFlagArg(DEVPOD_FLAG_PREBUILD_REPOSITORY, config.prebuildRepositories.join(","))] : [] const maybeDevcontainerPath = config.devcontainerPath ? [toFlagArg(DEVPOD_FLAG_DEVCONTAINER_PATH, config.devcontainerPath)] : [] const additionalFlags = [] if (WorkspaceCommands.ADDITIONAL_FLAGS.size > 0) { for (const [key, value] of WorkspaceCommands.ADDITIONAL_FLAGS.entries()) { if (key === WORKSPACE_COMMAND_ADDITIONAL_FLAGS_KEY) { additionalFlags.push(value) continue } additionalFlags.push(toFlagArg(key, value)) } } return WorkspaceCommands.newCommand([ DEVPOD_COMMAND_UP, identifier, ...maybeIDFlag, ...maybeSourceFlag, ...maybeIDEFlag, ...maybeProviderFlag, ...maybePrebuildRepositories, ...maybeDevcontainerPath, ...additionalFlags, ...maybeProviderOptionsFlag, DEVPOD_FLAG_JSON_LOG_OUTPUT, ]) } static StopWorkspace(id: TWorkspaceID) { return WorkspaceCommands.newCommand([DEVPOD_COMMAND_STOP, id, DEVPOD_FLAG_JSON_LOG_OUTPUT]) } static RebuildWorkspace(id: TWorkspaceID) { return WorkspaceCommands.newCommand([ DEVPOD_COMMAND_UP, id, DEVPOD_FLAG_JSON_LOG_OUTPUT, DEVPOD_FLAG_RECREATE, ]) } static ResetWorkspace(id: TWorkspaceID) { return WorkspaceCommands.newCommand([ DEVPOD_COMMAND_UP, id, DEVPOD_FLAG_JSON_LOG_OUTPUT, DEVPOD_FLAG_RESET, ]) } static TroubleshootWorkspace(id: TWorkspaceID) { return WorkspaceCommands.newCommand([DEVPOD_COMMAND_TROUBLESHOOT, id]) } static RemoveWorkspace(id: TWorkspaceID, force?: boolean) { const args = [DEVPOD_COMMAND_DELETE, id, DEVPOD_FLAG_JSON_LOG_OUTPUT] if (force) { args.push(DEVPOD_FLAG_FORCE) } return WorkspaceCommands.newCommand(args) } static GetDevcontainerConfig(rawSource: string) { return new Command([ DEVPOD_COMMAND_HELPER, DEVPOD_COMMAND_GET_WORKSPACE_CONFIG, rawSource, DEVPOD_FLAG_TIMEOUT, "10s", ]) } } ================================================ FILE: desktop/src/components/Animation/Ripple.tsx ================================================ import { Icon, IconProps } from "@chakra-ui/react" import { motion } from "framer-motion" const initial = { r: 4, opacity: 0.3, } const animate = { r: 12, opacity: 0 } const transition = { duration: 4, repeat: Infinity } export function Ripple(props: IconProps) { return ( ) } ================================================ FILE: desktop/src/components/Animation/index.ts ================================================ export { Ripple } from "./Ripple" ================================================ FILE: desktop/src/components/AutoComplete/AutoComplete.tsx ================================================ import { Box, Fade, Icon, Input, InputGroup, InputRightElement, useColorModeValue, useToken, } from "@chakra-ui/react" import { Combobox } from "@headlessui/react" import { forwardRef, useEffect, useRef, useState } from "react" import { AiOutlineCaretRight } from "react-icons/ai" type TAutoCompleteOption = Readonly<{ key: string label: string }> type TAutoCompleteProps = Readonly<{ options: readonly TAutoCompleteOption[] onChange?: (value: string) => void onBlur?: () => void value?: string defaultValue?: string placeholder?: string name?: string }> /* * Can be integrated with `react-hook-form` like this: * ```tsx const { handleSubmit, control } = useForm()
} /> ``` */ export const AutoComplete = forwardRef(function InnerAutoComplete( { name, placeholder, options, defaultValue, value, onChange, onBlur }, ref ) { const openButtonRef = useRef(null) const optionsBackgroundColor = useColorModeValue("gray.100", "gray.800") const optionsZIndex = useToken("zIndices", "dropdown") const [query, setQuery] = useState("") useEffect(() => { // set value initially if (value) { onChange?.(value) } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const filteredOptions = query === "" ? options : options.filter((option) => { return option.key.toLowerCase().includes(query.toLowerCase()) }) function handleInputFocused(isOpen: boolean) { return () => { if (!isOpen) { openButtonRef.current?.click() } } } return ( ref={ref} name={name} value={value} defaultValue={defaultValue} onChange={onChange}> {({ open: isOpen }) => ( { setQuery(event.target.value) onChange?.(event.target.value) }} onClick={handleInputFocused(isOpen)} /> { e.preventDefault() e.stopPropagation() }}> {({ open: isOpen }) => ( {query.length > 0 && !filteredOptions.find((o) => o.label === query) && ( )} )} ) }) function Option({ option }: { option: TAutoCompleteOption }) { const activeOptionBackgroundColor = useColorModeValue("gray.200", "gray.700") return ( {({ active }) => ( {option.label} )} ) } ================================================ FILE: desktop/src/components/AutoComplete/index.ts ================================================ export { AutoComplete } from "./AutoComplete" ================================================ FILE: desktop/src/components/BottomActionBar/BottomActionBar.tsx ================================================ import { SIDEBAR_WIDTH } from "@/constants" import { ExclamationCircle } from "@/icons" import { exists, isError } from "@/lib" import { BoxProps, HStack, IconButton, Popover, PopoverContent, PopoverTrigger, useBreakpointValue, useColorModeValue, } from "@chakra-ui/react" import { motion } from "framer-motion" import { RefObject, createContext, useContext, useEffect, useMemo, useRef, useState } from "react" import { ErrorMessageBox } from "../Error" import { useBorderColor } from "@/Theme" type TModalBottomBarProps = Readonly<{ isModal?: boolean hasSidebar?: boolean stickToBottom?: boolean children: React.ReactNode }> const BottomActionBarContext = createContext<{ ref: RefObject } | undefined>( undefined ) export function BottomActionBar({ isModal = false, hasSidebar = true, stickToBottom, children, }: TModalBottomBarProps) { const ref = useRef(null) const bottomBarBackgroundColor = useColorModeValue("white", "gray.900") const bottomBarBackgroundColorModal = useColorModeValue("white", "background.darkest") const borderColor = useBorderColor() const translateX = useBreakpointValue({ base: hasSidebar ? "translateX(-3rem)" : "", xl: isModal ? "translateX(-3rem)" : "", }) const paddingX = useBreakpointValue({ base: "3rem", xl: isModal ? "3rem" : "4" }) const value = useMemo(() => ({ ref }), [ref]) const width = useMemo(() => { if (isModal) { return "calc(100% + 5.5rem)" } if (hasSidebar) { return { base: `calc(100vw - ${SIDEBAR_WIDTH})`, xl: "full" } } return { base: "100vw", xl: "full" } }, [hasSidebar, isModal]) const otherProps = useMemo(() => { if (stickToBottom) { return { position: "fixed", bottom: "2rem", } } return { position: "sticky", bottom: "-1.1rem", } }, [stickToBottom]) return ( {children} ) } type TBottomActionBarErrorProps = Readonly<{ error?: Error | null containerRef?: RefObject }> export function BottomActionBarError({ error, containerRef }: TBottomActionBarErrorProps) { const ctx = useContext(BottomActionBarContext) const { height, width } = useErrorDimensions(containerRef, ctx?.ref) // Open error popover when error changes const errorButtonRef = useRef(null) useEffect(() => { if (error) { errorButtonRef.current?.click() } }, [error]) return ( } isDisabled={!exists(error)} /> {isError(error) && } ) } function useErrorDimensions( ref: RefObject | undefined, _parentRef: RefObject | undefined, defaultHeight: BoxProps["height"] = "5xl", defaultWidth: BoxProps["width"] = "5xl" ) { const [errorHeight, setErrorHeight] = useState(defaultHeight) const [errorWidth] = useState(defaultWidth) useEffect(() => { const curr = ref?.current if (!curr) { return } const observer = new ResizeObserver((entries) => { for (const entry of entries) { if (entry.target === curr) { const heightPx = entry.contentRect.height setErrorHeight(`calc(${heightPx}px - 4rem)`) } } }) observer.observe(curr) return () => observer.disconnect() }, [ref]) return { height: errorHeight, width: errorWidth } } ================================================ FILE: desktop/src/components/BottomActionBar/index.ts ================================================ export { BottomActionBar, BottomActionBarError } from "./BottomActionBar" ================================================ FILE: desktop/src/components/CardHeader/WorkspaceCardHeader.tsx ================================================ import { Box, Checkbox, HStack, Heading, Icon, Text, VStack } from "@chakra-ui/react" import dayjs from "dayjs" import { ReactNode, useId } from "react" import { HiClock, HiOutlineCode } from "react-icons/hi" import { useNavigate } from "react-router" import { IconTag } from "@/components" import { Stack3D } from "@/icons" import { Routes } from "@/routes" type TWorkspaceCardHeaderProps = Readonly<{ id: string statusBadge?: ReactNode controls?: ReactNode children?: ReactNode source?: ReactNode isSelected?: boolean onSelectionChange?: (isSelected: boolean) => void }> export function WorkspaceCardHeader({ id, isSelected, onSelectionChange, statusBadge, controls, source, children, }: TWorkspaceCardHeaderProps) { const checkboxID = useId() return ( <> {onSelectionChange && ( onSelectionChange(e.target.checked)} /> )} {id} {statusBadge} {controls} {source} {children} ) } type TProviderProps = Readonly<{ name: string | undefined }> function Provider({ name }: TProviderProps) { const navigate = useNavigate() return ( } label={name ?? "No provider"} info={name ? `Uses provider ${name}` : undefined} onClick={() => { if (!name) { return } navigate(Routes.toProvider(name)) }} /> ) } WorkspaceCardHeader.Provider = Provider type TIDEProps = Readonly<{ name: string }> function IDE({ name }: TIDEProps) { return ( } label={name} info={`Will be opened in ${name}`} /> ) } WorkspaceCardHeader.IDE = IDE type TLastUsedProps = Readonly<{ timestamp: string }> function LastUsed({ timestamp }: TLastUsedProps) { return ( } label={dayjs(new Date(timestamp)).fromNow()} info={`Last used ${dayjs(new Date(timestamp)).fromNow()}`} /> ) } WorkspaceCardHeader.LastUsed = LastUsed ================================================ FILE: desktop/src/components/CardHeader/index.ts ================================================ export { WorkspaceCardHeader } from "./WorkspaceCardHeader" ================================================ FILE: desktop/src/components/DeleteWorkspacesModal/DeleteWorkspacesModal.tsx ================================================ import { Box, Button, Checkbox, HStack, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, } from "@chakra-ui/react" import React, { useCallback, useState } from "react" export function DeleteWorkspacesModal({ onDeleteRequested, onCloseRequested, amount, isOpen, pro = false, }: { isOpen: boolean onCloseRequested: () => void onDeleteRequested: (forceDelete: boolean) => void amount: number pro?: boolean }) { const [forceDelete, setForceDelete] = useState(false) const onDeleteClick = useCallback(() => { onCloseRequested() onDeleteRequested(pro ? true : forceDelete) }, [forceDelete, onDeleteRequested, onCloseRequested]) const onForceDeleteChanged = useCallback( (e: React.ChangeEvent) => { setForceDelete(e.target.checked) }, [setForceDelete] ) return ( Delete {amount} Workspaces Deleting the workspaces will erase all state. Are you sure you want to delete the selected workspaces? {!pro && ( Force Delete )} ) } ================================================ FILE: desktop/src/components/DeleteWorkspacesModal/index.ts ================================================ export * from "./DeleteWorkspacesModal" ================================================ FILE: desktop/src/components/Error/ErrorMessageBox.tsx ================================================ import { Box, BoxProps, Text, useColorModeValue } from "@chakra-ui/react" import React from "react" type TErrorMessageBox = Readonly<{ error: Error }> & BoxProps export function ErrorMessageBox({ error, ...boxProps }: TErrorMessageBox) { const backgroundColor = useColorModeValue("red.50", "red.100") const textColor = useColorModeValue("red.700", "red.800") return ( {error.message.split("\n").map((line, index) => ( {line}
))}
) } ================================================ FILE: desktop/src/components/Error/index.ts ================================================ export { ErrorMessageBox } from "./ErrorMessageBox" ================================================ FILE: desktop/src/components/ExampleCard.tsx ================================================ import { BoxProps, Card, Image, ImageProps, Text, Tooltip, forwardRef, useColorModeValue, useToken, } from "@chakra-ui/react" import { ComponentType, ReactElement, cloneElement, useMemo } from "react" type TExampleCardProps = { name: string image?: string | ReactElement size?: keyof typeof sizes isSelected?: boolean isDisabled?: boolean showTooltip?: boolean onClick?: () => void } & BoxProps const sizes: Record<"sm" | "md" | "lg", BoxProps["width"]> = { sm: "12", md: "16", lg: "20", } as const export const ExampleCard = forwardRef>( function InnerExampleCard( { name, image, isSelected, isDisabled, size = "lg", showTooltip = true, onClick, ...restBoxProps }, ref ) { const hoverBackgroundColor = useColorModeValue("gray.50", "gray.800") const primaryColorLight = useToken("colors", "primary.400") const primaryColorDark = useToken("colors", "primary.800") const selectedProps = isSelected ? { _before: { content: '""', position: "absolute", top: 0, bottom: 0, left: 0, right: 0, background: `linear-gradient(135deg, ${primaryColorLight}55 30%, ${primaryColorDark}55, ${primaryColorDark}88)`, opacity: 0.7, width: "full", height: "full", }, boxShadow: `inset 0px 0px 0px 1px ${primaryColorDark}55`, } : {} const disabledProps = isDisabled ? { filter: "grayscale(100%)", cursor: "not-allowed" } : {} const imageElement = useMemo(() => { if (image === undefined) { return null } const imageProps: ImageProps = { objectFit: "fill", overflow: "hidden", zIndex: "1" } if (typeof image === "string") { return } return cloneElement(image, imageProps) }, [image]) return ( {imageElement} {size === "lg" && ( {name} )} ) } ) ================================================ FILE: desktop/src/components/Form/Form.tsx ================================================ import { Box, BoxProps, forwardRef } from "@chakra-ui/react" type TFormProps = Readonly<{ ref?: React.Ref children: React.ReactNode onSubmit?: React.FormEventHandler }> & Omit export const Form = forwardRef(function InnerForm( { children, onSubmit, ...boxProps }, ref ) { return ( {children} ) }) ================================================ FILE: desktop/src/components/Form/index.ts ================================================ export { Form } from "./Form" ================================================ FILE: desktop/src/components/IDEGroup/IDEGroup.tsx ================================================ import { TIDE } from "@/types" import { getIDEDisplayName, useHover } from "@/lib" import { HStack, MenuItem, PlacementWithLogical, Popover, PopoverContent, PopoverTrigger, Portal, Text, useColorModeValue, } from "@chakra-ui/react" import { ChevronRightIcon } from "@chakra-ui/icons" import { IDEIcon } from "@/components" import { useCallback, useEffect } from "react" export function IDEGroup({ ides, group, disabled, onItemClick, placement, offset, onHoverChange, }: { ides?: TIDE[] group: string disabled?: boolean onItemClick: (ide: TIDE["name"]) => void placement?: PlacementWithLogical offset?: [number, number] onHoverChange?: (group: string, hover: boolean) => void }) { const [popoverHover, popoverRef] = useHover() const [triggerHover, triggerRef] = useHover() useEffect(() => { onHoverChange?.(group, popoverHover || triggerHover) }, [popoverHover, triggerHover, onHoverChange, group]) return ( {group} {ides?.map((ide) => ( ))} ) } function IDEItem({ disabled, onItemClick, ide, }: { ide: TIDE onItemClick: (ide: TIDE["name"]) => void disabled?: boolean }) { const menuHoverColor = useColorModeValue("gray.100", "gray.700") const onClick = useCallback(() => { onItemClick(ide.name) }, [onItemClick, ide]) return ( }> {getIDEDisplayName(ide)} ) } ================================================ FILE: desktop/src/components/IDEGroup/index.ts ================================================ export * from "./IDEGroup" ================================================ FILE: desktop/src/components/IDEIcon/IDEIcon.tsx ================================================ import { Box, BoxProps, Icon, IconProps, Image, useColorMode, useColorModeValue, useToken, } from "@chakra-ui/react" import { HiBeaker } from "react-icons/hi2" import { CLionSvg, CodiumSvg, CursorSvg, DataSpellSvg, FleetSvg, GolandSvg, IntelliJSvg, JupyterNotebookDarkSvg, JupyterNotebookSvg, NoneSvg, NoneSvgDark, PHPStormSvg, PositronSvg, PycharmSvg, RStudioSvg, RiderSvg, RubyMineSvg, RustRoverSvg, VSCodeBrowser, VSCodeInsidersSvg, VSCodeSvg, WebstormSvg, WindsurfSvg, ZedDarkSvg, ZedSvg, } from "../../images" import { TIDE } from "../../types" import { useMemo } from "react" const SIZES: Record, IconProps> = { sm: { boxSize: 3, padding: "1px", }, md: { boxSize: 6, padding: "3px", }, } const IDE_ICONS: Record = { none: NoneSvg, vscode: VSCodeSvg, "vscode-insiders": VSCodeInsidersSvg, openvscode: VSCodeBrowser, intellij: IntelliJSvg, goland: GolandSvg, rustrover: RustRoverSvg, pycharm: PycharmSvg, phpstorm: PHPStormSvg, clion: CLionSvg, rubymine: RubyMineSvg, rider: RiderSvg, webstorm: WebstormSvg, dataspell: DataSpellSvg, fleet: FleetSvg, jupyternotebook: JupyterNotebookSvg, jupyternotebook_dark: JupyterNotebookDarkSvg, cursor: CursorSvg, positron: PositronSvg, codium: CodiumSvg, zed: ZedSvg, zed_dark: ZedDarkSvg, rstudio: RStudioSvg, windsurf: WindsurfSvg, } type TIDEIconProps = Readonly<{ ide: TIDE; size?: "sm" | "md" }> & BoxProps export function IDEIcon({ ide, size = "md", ...boxProps }: TIDEIconProps) { const experimentalIconSizeProps = SIZES[size] const primaryColorDarkToken = useColorModeValue("primary.800", "primary.400") const primaryColorDark = useToken("colors", primaryColorDarkToken) const primaryColorLightToken = useColorModeValue("primary.400", "primary.800") const primaryColorLight = useToken("colors", primaryColorLightToken) const backgroundColor = useColorModeValue("white", "gray.700") const { colorMode } = useColorMode() const experimentalIconStylingProps = size === "sm" ? { color: primaryColorDark, } : { boxShadow: `inset 0px 0px 0px 1px ${primaryColorDark}55`, background: colorMode === "light" ? `linear-gradient(135deg, ${primaryColorLight}55 50%, ${primaryColorDark}55, ${primaryColorDark}88)` : `linear-gradient(135deg, ${primaryColorDark}55 50%, ${primaryColorLight}55, ${primaryColorLight}88)`, color: `${primaryColorDark}CC`, } const fallbackIcon = colorMode === "light" ? NoneSvg : NoneSvgDark const icon = useMemo(() => { if (colorMode === "light") { return IDE_ICONS[ide.name!] ?? ide.icon } else { const darkIcon = IDE_ICONS[ide.name! + "_dark"] ?? ide.iconDark if (darkIcon) { return darkIcon } // fall back to regular icon return IDE_ICONS[ide.name!] ?? ide.icon } }, [colorMode, ide]) return ( {ide.experimental && ( <> )} ) } ================================================ FILE: desktop/src/components/IDEIcon/index.ts ================================================ export { IDEIcon } from "./IDEIcon" ================================================ FILE: desktop/src/components/Layout/NavigationViewLayout.tsx ================================================ import { Heading, HStack } from "@chakra-ui/react" import { ReactNode } from "react" import { exists } from "../../lib" import { ToolbarTitle } from "./Toolbar" import { TViewTitle } from "./types" type TNavigationViewLayoutProps = Readonly<{ title: TViewTitle | null; children?: ReactNode }> export function NavigationViewLayout({ title, children }: TNavigationViewLayoutProps) { return ( <> {exists(title) && ( {exists(title.leadingAction) && title.leadingAction} {title.label} {exists(title.trailingAction) && title.trailingAction} )} {children} ) } ================================================ FILE: desktop/src/components/Layout/Notifications.tsx ================================================ import { Badge, Box, Button, Center, Divider, HStack, Heading, IconButton, Image, Link, LinkBox, LinkOverlay, Popover, PopoverArrow, PopoverBody, PopoverContent, PopoverHeader, PopoverTrigger, Portal, Spinner, Text, VStack, useColorModeValue, } from "@chakra-ui/react" import dayjs from "dayjs" import { JSX, ReactNode, useMemo } from "react" import { Link as RouterLink, To, useLocation } from "react-router-dom" import { client } from "../../client" import { TActionObj, useAllWorkspaceActions, useSettings } from "../../contexts" import { Bell, CheckCircle, ExclamationCircle, ExclamationTriangle } from "../../icons" import { getActionDisplayName, useUpdate } from "../../lib" import { Ripple } from "../Animation" type TNotificationsProps = Readonly<{ badgeNumber?: number providerUpdates?: ReactNode icon?: JSX.Element getActionDestination: (action: TActionObj) => To }> export function Notifications({ icon, badgeNumber = 0, providerUpdates, getActionDestination, }: TNotificationsProps) { const location = useLocation() const actions = useAllWorkspaceActions() const backgroundColor = useColorModeValue("white", "gray.900") const subheadingTextColor = useColorModeValue("gray.500", "gray.400") const actionHoverColor = useColorModeValue("gray.100", "gray.700") const hasActiveActions = actions.active.length > 0 const settings = useSettings() const { pendingUpdate, install: installUpdate, isInstalling, isInstallDisabled } = useUpdate() const combinedActions = useMemo(() => { return [...actions.active, ...actions.history] }, [actions.active, actions.history]) const maybeIconColor = useMemo(() => icon?.props.color, [icon]) return (
{icon ? icon : } {(pendingUpdate || badgeNumber !== 0) && ( {pendingUpdate ? 1 + badgeNumber : badgeNumber} )} {hasActiveActions && } } />
Notifications {pendingUpdate && ( {pendingUpdate.tag_name} is available See{" "} client.open(pendingUpdate.html_url)}>release notes )} {combinedActions.length === 0 && badgeNumber === 0 && ( No notifications )} {providerUpdates && ( <> {providerUpdates} )} {combinedActions.map((action) => ( {action.status === "pending" ? ( settings.partyParrot ? ( ) : ( ) ) : null} {action.status === "success" && } {action.status === "error" && } {action.status === "cancelled" && } {getActionDisplayName(action)} {action.finishedAt !== undefined && ( {dayjs(action.finishedAt).fromNow()} )} ))}
) } ================================================ FILE: desktop/src/components/Layout/ProLayout.tsx ================================================ import { useBorderColor } from "@/Theme" import { STATUS_BAR_HEIGHT } from "@/constants" import { Box, Flex, HStack, useColorModeValue, useToken } from "@chakra-ui/react" import { ReactNode } from "react" import { StatusBar } from "./StatusBar" import { Toolbar } from "./Toolbar" import { isMacOS } from "@/lib" type TProLayoutProps = Readonly<{ toolbarItems: ReactNode statusBarItems: ReactNode children: ReactNode }> export function ProLayout({ toolbarItems, statusBarItems, children }: TProLayoutProps) { const contentBackgroundColor = useColorModeValue("white", "background.darkest") const toolbarHeight = useToken("sizes", "10") const statusBarHeight = useToken("sizes", "8") const borderColor = useBorderColor() return ( {toolbarItems} {children} {statusBarItems} ) } ================================================ FILE: desktop/src/components/Layout/ProSwitcher.tsx ================================================ import { client } from "@/client" import { useProInstances, useProviders, useSettings } from "@/contexts" import { CheckCircle, CircleWithArrow, DevPodProBadge, ExclamationTriangle } from "@/icons" import { exists, canHealthCheck as isNewProProvider, useLoginProModal, useReLoginProModal, } from "@/lib" import { Routes } from "@/routes" import { TProID, TProInstance, TProInstances, TProviderConfig } from "@/types" import { useDeleteProviderModal } from "@/views/Providers/useDeleteProviderModal" import { ChevronDownIcon, CloseIcon } from "@chakra-ui/icons" import { Box, Button, ButtonGroup, HStack, Heading, Icon, IconButton, Link, List, ListItem, Popover, PopoverArrow, PopoverBody, PopoverContent, PopoverHeader, PopoverTrigger, Portal, Text, Tooltip, VStack, useColorModeValue, } from "@chakra-ui/react" import dayjs from "dayjs" import { Dispatch, ReactElement, SetStateAction, useEffect, useMemo, useState } from "react" import { HiArrowRightOnRectangle, HiClock } from "react-icons/hi2" import { useNavigate } from "react-router-dom" import { IconTag } from "../Tag" type TProInstanceWithProvider = TProInstance & Readonly<{ providerConfig: TProviderConfig | null }> export function ProSwitcher() { const [[proInstances]] = useProInstances() const { modal: loginProModal, handleOpenLogin: handleConnectClicked } = useLoginProModal() const { modal: reLoginProModal, handleOpenLogin: handleReLoginClicked } = useReLoginProModal() const [isDeleting, setIsDeleting] = useState(false) const backgroundColor = useColorModeValue("white", "gray.900") const handleAnnouncementClicked = () => { client.open("https://devpod.sh/pro") } const { experimental_devPodPro } = useSettings() const isProUnauthenticated = proInstances?.some(({ authenticated }) => !authenticated) if (!experimental_devPodPro) { return ( ) } return ( <> handleReLoginClicked({ host })} emptyProInstances={} /> {loginProModal} {reLoginProModal} ) } type TProPopoverContentProps = Readonly<{ proInstances: TProInstances | undefined emptyProInstances: ReactElement setIsDeleting: Dispatch> onConnect: VoidFunction onReLogin: (host: string) => void }> function ProPopoverContent({ proInstances, emptyProInstances, setIsDeleting, onConnect, onReLogin, }: TProPopoverContentProps) { const navigate = useNavigate() const hoverBgColor = useColorModeValue("gray.100", "gray.700") const [[providers]] = useProviders() const { newProInstances, legacyProInstances } = useMemo(() => { return ( proInstances ?.map((instance) => { if (!instance.provider) { return { ...instance, providerConfig: null } } const providerConfig = providers?.[instance.provider]?.config if (!providerConfig) { return { ...instance, providerConfig: null } } return { ...instance, providerConfig } }) .reduce( (acc, curr) => { if (!curr.providerConfig) { acc.legacyProInstances.push(curr) return acc } if (!isNewProProvider(curr.providerConfig)) { acc.legacyProInstances.push(curr) return acc } acc.newProInstances.push(curr) return acc }, { newProInstances: [] as TProInstanceWithProvider[], legacyProInstances: [] as TProInstanceWithProvider[], } ) ?? { newProInstances: [] as TProInstanceWithProvider[], legacyProInstances: [] as TProInstanceWithProvider[], } ) }, [proInstances, providers]) return ( <> Your Pro Instances Manage DevPod Pro } /> {proInstances === undefined || (proInstances.length === 0 && emptyProInstances)} {legacyProInstances.map((proInstance) => { const host = proInstance.host if (!host) { return null } return ( onReLogin(host)} /> ) })} {newProInstances.map(({ host, authenticated }) => { if (!host) { return null } return ( ) })} ) } type TEmptyProInstancesProps = Readonly<{ onConnect: VoidFunction }> function EmptyProInstances({ onConnect }: TEmptyProInstancesProps) { return ( No Pro instances You don't have any Pro instances set up. Connect to an existing Instance or create a new one.
client.open("https://devpod.sh/pro")}> Learn more
) } type TProInstaceRowProps = Omit & Readonly<{ host: TProID onIsDeletingChanged: (isDeleting: boolean) => void onLoginClicked?: VoidFunction }> function ProInstanceRow({ host, creationTimestamp, onIsDeletingChanged, authenticated, onLoginClicked, }: TProInstaceRowProps) { const [, { disconnect }] = useProInstances() const { modal: deleteProviderModal, open: openDeleteProviderModal, isOpen, } = useDeleteProviderModal(host, "Pro instance", "disconnect", () => disconnect.run({ id: host })) useEffect(() => { onIsDeletingChanged(isOpen) // `onIsDeletingChanged` is expected to be a stable reference // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpen]) return ( <> {host} {exists(authenticated) && ( ) : ( ) } label="" paddingInlineStart="0" info={authenticated ? "Authenticated" : "Not Authenticated"} {...(authenticated ? {} : { onClick: onLoginClicked, cursor: "pointer" })} /> )} {exists(creationTimestamp) && ( } label={dayjs(new Date(creationTimestamp)).format("MMM D, YY")} info={`Created ${dayjs(new Date(creationTimestamp)).fromNow()}`} /> )} {exists(host) && ( } /> )} {deleteProviderModal} ) } ================================================ FILE: desktop/src/components/Layout/Sidebar.tsx ================================================ import { useSystemTheme } from "@/lib" import { Box, BoxProps, Flex, Grid, HStack, Link, Text, useColorMode, useColorModeValue, useToken, VStack, } from "@chakra-ui/react" import { cloneElement, ReactElement, ReactNode } from "react" import { LinkProps, NavLink as RouterLink } from "react-router-dom" import { useSettings } from "../../contexts" import { DevpodWordmark } from "../../icons" import { useBorderColor } from "../../Theme" import { LoftOSSBadge } from "../LoftOSSBadge" type TSidebarProps = Readonly<{ children?: readonly ReactElement[] }> & BoxProps export function Sidebar({ children, ...boxProps }: TSidebarProps) { const { colorMode } = useColorMode() const systemTheme = useSystemTheme() const borderColor = useBorderColor() const backgroundColor = useColorModeValue("white", "black") const alternativeBackgroundColor = useColorModeValue("gray.100", "gray.900") const wordmarkColor = useColorModeValue("black", "white") const isLeft = useSettings().sidebarPosition === "left" const { transparency } = useSettings() const sharedBackgroundMaterialProps = { "aria-hidden": true, width: "full", height: "full", position: "absolute", zIndex: -1, } as const return ( {children} {/* Background Material */} {transparency ? ( ) : ( )} ) } type TSidebarMenuProps = Pick & Readonly<{ children?: ReactNode; icon: ReactElement }> export function SidebarMenuItem({ to, children, icon: iconProps }: TSidebarMenuProps) { const settings = useSettings() const backgroundColorToken = useColorModeValue("gray.300", "gray.700") const backgroundColor = useToken("colors", backgroundColorToken) const borderColorToken = useColorModeValue("primary.600", "primary.600") const borderColor = useToken("colors", borderColorToken) const backgroundActiveColorToken = useColorModeValue("primary.500", "primary.700") const backgroundActiveColor = useToken("colors", backgroundActiveColorToken) const isLeft = settings.sidebarPosition === "left" const icon = cloneElement(iconProps, { boxSize: 4 }) return ( ({ ...(isActive ? { backgroundColor: backgroundActiveColor, color: "white", borderColor, opacity: 1, } : {}), })}> {icon} {children} ) } ================================================ FILE: desktop/src/components/Layout/StatusBar.tsx ================================================ import { CheckIcon, StarIcon } from "@chakra-ui/icons" import { BoxProps, Button, Checkbox, HStack, Icon, IconButton, Input, Menu, MenuButton, MenuItem, MenuList, Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay, Text, Tooltip, useColorModeValue, useDisclosure, } from "@chakra-ui/react" import { useMemo, useRef } from "react" import { FaBug } from "react-icons/fa" import { HiDocumentMagnifyingGlass, HiMagnifyingGlassPlus } from "react-icons/hi2" import { client } from "../../client" import { useChangeSettings } from "../../contexts" import { Debug, useArch, useDebug, usePlatform, useVersion } from "../../lib" export function StatusBar(boxProps: BoxProps) { return ( ) } function Version() { const version = useVersion() return {version ?? "unknown version"} } function Platform() { const platform = usePlatform() return {platform ?? "unknown platform"} } function Arch() { const arch = useArch() return {arch ?? "unknown arch"} } function ZoomMenu() { const { settings, set } = useChangeSettings() const zoomIcon = useMemo(() => { return { icon: } }, []) const textColor = useColorModeValue("gray.900", "gray.300") return ( } /> set("zoom", "sm")} {...(settings.zoom === "sm" ? zoomIcon : {})}> Small set("zoom", "md")} {...(settings.zoom === "md" ? zoomIcon : {})}> Regular set("zoom", "lg")} {...(settings.zoom === "lg" ? zoomIcon : {})}> Large set("zoom", "xl")} {...(settings.zoom === "xl" ? zoomIcon : {})}> Extra Large ) } function GitHubStar() { const iconColor = useStatusBarIconColor() return ( } aria-label="Loving DevPod? Give us a star on Github" onClick={() => client.open("https://github.com/loft-sh/devpod")} /> ) } function OSSDocs() { const iconColor = useStatusBarIconColor() return ( } aria-label="How to DevPod - Docs" onClick={() => client.open("https://devpod.sh/docs")} /> ) } function OSSReportIssue() { const iconColor = useStatusBarIconColor() return ( } aria-label="Report an Issue" onClick={() => client.open("https://github.com/loft-sh/devpod/issues/new/choose")} /> ) } function DebugMenu() { const inputRef = useRef(null) const debug = useDebug() const { isOpen, onClose, onOpen: openModal } = useDisclosure() if (!debug.isEnabled) { return null } const handleMenuItemClicked = (option: Parameters>[0]) => (e: React.MouseEvent) => { Debug.toggle?.(option) e.stopPropagation() } const handleImportLinkClicked = () => { const rawLink = inputRef.current?.value if (!rawLink) { return } const url = new URL(rawLink.replace(/#/g, "?")) const workspaceUID = url.searchParams.get("workspace-uid") const workspaceID = url.searchParams.get("workspace-id") const host = url.searchParams.get("devpod-pro-host") const project = url.searchParams.get("project") if (!workspaceUID || !workspaceID || !host || !project) { console.error( "Some parameters are missing for import", url, Array.from(url.searchParams.entries()) ) return } client.emitEvent({ type: "ImportWorkspace", workspace_uid: workspaceUID, workspace_id: workspaceID, devpod_pro_host: host, project, options: {}, }) onClose() } return ( <> Debug Print command logs Print action logs Print workspace logs { client.openDir("AppData") e.stopPropagation() }}> Open app_dir { openModal() e.stopPropagation() }}> Open import tool Import workspace Paste a platform import link here ) } function useStatusBarIconColor() { return useColorModeValue("iconColor", "gray.400") } StatusBar.Version = Version StatusBar.Platform = Platform StatusBar.Arch = Arch StatusBar.ZoomMenu = ZoomMenu StatusBar.GitHubStar = GitHubStar StatusBar.OSSDocs = OSSDocs StatusBar.OSSReportIssue = OSSReportIssue StatusBar.DebugMenu = DebugMenu ================================================ FILE: desktop/src/components/Layout/Toolbar.tsx ================================================ import { Box, BoxProps } from "@chakra-ui/react" import { ReactNode, useEffect, useId } from "react" import { useBorderColor } from "../../Theme" import { useToolbar } from "../../contexts" export function Toolbar({ ...boxProps }: BoxProps) { const borderColor = useBorderColor() return } function Title() { const { title } = useToolbar() return <>{title} } function Actions() { const { actions } = useToolbar() return <>{actions} } Toolbar.Title = Title Toolbar.Actions = Actions export function ToolbarTitle({ children }: Readonly<{ children: ReactNode }>) { const { setTitle } = useToolbar() useEffect(() => { setTitle(children) }, [children, setTitle]) return null } export function ToolbarActions({ children }: Readonly<{ children: ReactNode }>) { const { addAction } = useToolbar() const id = useId() useEffect(() => { const removeActions = addAction(id, children) return removeActions }, [children, addAction, id]) return null } ================================================ FILE: desktop/src/components/Layout/index.ts ================================================ export { NavigationViewLayout } from "./NavigationViewLayout" export { Sidebar, SidebarMenuItem } from "./Sidebar" export type { TViewTitle } from "./types" export { Toolbar, ToolbarTitle, ToolbarActions } from "./Toolbar" export { StatusBar } from "./StatusBar" export { Notifications } from "./Notifications" export { ProSwitcher } from "./ProSwitcher" export { ProLayout } from "./ProLayout" ================================================ FILE: desktop/src/components/Layout/types.ts ================================================ import { ReactElement } from "react" export type TViewTitle = Readonly<{ label: string priority: "high" | "regular" leadingAction?: ReactElement trailingAction?: ReactElement }> ================================================ FILE: desktop/src/components/ListSelection/ListSelection.tsx ================================================ import { Pause, Trash } from "@/icons" import { Button, Checkbox, FormLabel, HStack } from "@chakra-ui/react" import { ThemeTypings } from "@chakra-ui/styled-system" import { ReactElement, useMemo } from "react" type TSelectionAction = { label: string icon?: ReactElement perform?: () => unknown style?: { colorScheme?: ThemeTypings["colorSchemes"] } } type TListSelectionProps = { totalAmount: number selectionAmount: number handleSelectAllClicked?: () => void selectionActions?: TSelectionAction[] } export function ListSelection({ totalAmount, selectionAmount, handleSelectAllClicked, selectionActions, }: TListSelectionProps) { return ( 0 && selectionAmount < totalAmount} isChecked={totalAmount > 0 && selectionAmount === totalAmount} onChange={handleSelectAllClicked} /> {selectionAmount === 0 ? "Select all" : ` ${selectionAmount} of ${totalAmount} selected`} {selectionAmount > 0 && ( <> {selectionActions?.map((action, index) => ( ))} )} ) } type TWorkspaceListSelectionProps = Omit & { handleDeleteClicked?: () => void handleStopAllClicked?: () => void } export function WorkspaceListSelection({ handleDeleteClicked, handleStopAllClicked, ...props }: TWorkspaceListSelectionProps) { const actions: TSelectionAction[] = useMemo( () => [ { label: "Stop", icon: , perform: handleStopAllClicked, }, { label: "Delete", icon: , perform: handleDeleteClicked, style: { colorScheme: "red", }, }, ], [handleStopAllClicked, handleDeleteClicked] ) return } ================================================ FILE: desktop/src/components/ListSelection/index.ts ================================================ export * from "./ListSelection" ================================================ FILE: desktop/src/components/LoftOSSBadge.tsx ================================================ import { Link, Text } from "@chakra-ui/react" import { client } from "../client" import { Loft } from "../icons" export function LoftOSSBadge() { return ( client.open("https://loft.sh/")}> Open sourced by ) } ================================================ FILE: desktop/src/components/Section/CollapsibleSection.tsx ================================================ import { Box, BoxProps, Button, ButtonProps, Icon, useDisclosure } from "@chakra-ui/react" import { AnimatePresence, motion, Variants } from "framer-motion" import { forwardRef, ReactNode, useLayoutEffect, useRef } from "react" import { AiOutlineCaretRight } from "react-icons/ai" const variants: Variants = { enter: { opacity: 1, height: "auto", overflow: "revert", transition: { opacity: { duration: 0.2 }, height: { duration: 0.3 }, }, }, exit: { opacity: 0, height: 0, overflow: "hidden", transition: { opacity: { duration: 0.3 }, height: { duration: 0.2 }, }, }, } type TCollapsibleSectionProps = Readonly<{ title: ReactNode | ((isOpen: boolean) => ReactNode) children: ReactNode isOpen?: boolean isDisabled?: boolean showIcon?: boolean headerProps?: ButtonProps contentProps?: BoxProps onOpenChange?: (isOpen: boolean, element: HTMLDivElement | null) => void }> export const CollapsibleSection = forwardRef( function CollapsibleSection( { title, headerProps, contentProps, children, onOpenChange, isOpen: isOpenProp = false, isDisabled = false, showIcon, }, ref ) { const motionRef = useRef(null) const { isOpen, onOpen, onClose, getDisclosureProps, getButtonProps } = useDisclosure() const buttonProps = getButtonProps({ isDisabled }) const disclosureProps = getDisclosureProps() useLayoutEffect(() => { if (isOpenProp) { onOpen() } else { onClose() } }, [isOpenProp, onClose, onOpen]) return ( {isOpen && ( { if (definition === "exit") { onOpenChange?.(false, motionRef.current) } else { onOpenChange?.(true, motionRef.current) } }} style={{ display: "block", }}> {children} )} ) } ) ================================================ FILE: desktop/src/components/Section/index.ts ================================================ export { CollapsibleSection } from "./CollapsibleSection" ================================================ FILE: desktop/src/components/Steps/Steps.tsx ================================================ import { Box, Button, HStack, useColorModeValue } from "@chakra-ui/react" import { createContext, ReactNode, useCallback, useContext, useEffect, useId, useMemo, useState, } from "react" type TStepContext = Readonly<{ addStep: (id: string, step: ReactNode) => void }> type TStep = Readonly<{ id: string node: ReactNode }> const StepsContext = createContext(null!) type TStepsProps = Readonly<{ onFinish?: VoidFunction; finishText?: string; children: ReactNode }> export function Steps({ onFinish, finishText, children }: TStepsProps) { const [steps, setSteps] = useState([]) // Steps are 0 based for easier array indexing const [currentStep, setCurrentStep] = useState(0) const stepIndicatorInactiveColor = useColorModeValue("gray.200", "gray.700") const stepIndicatorActiveColor = useColorModeValue("primary.600", "primary.400") const isLastStep = useMemo(() => currentStep >= steps.length - 1, [currentStep, steps.length]) const isFirstStep = useMemo(() => currentStep <= 0, [currentStep]) const addStep = useCallback((id, node) => { setSteps((steps) => { const newSteps = steps.slice() const index = newSteps.findIndex((a) => a.id === id) if (index !== -1) { newSteps.splice(index, 1, { id, node: node }) } else { newSteps.push({ id, node: node }) } return newSteps }) return () => { setSteps((steps) => steps.filter((a) => a.id !== id)) } }, []) const handleBackClicked = useCallback(() => { if (isFirstStep) { return } setCurrentStep((currentStep) => currentStep - 1) }, [isFirstStep]) const handleNextClicked = useCallback(() => { if (isLastStep) { onFinish?.() return } setCurrentStep((currentStep) => currentStep + 1) }, [isLastStep, onFinish]) const value = useMemo(() => ({ addStep }), [addStep]) return ( <> {children} {steps[currentStep]?.node} {steps.map((_, i) => ( ))} ) } type TStepProps = Readonly<{ children: ReactNode }> export function Step({ children }: TStepProps) { const { addStep } = useContext(StepsContext) const id = useId() useEffect(() => { const removeStep = addStep(id, children) return removeStep }, [children, addStep, id]) return null } ================================================ FILE: desktop/src/components/Steps/index.ts ================================================ export { Steps, Step } from "./Steps" ================================================ FILE: desktop/src/components/Tag/IconTag.tsx ================================================ import { ButtonProps, Tag, TagLabel, TagProps, Tooltip } from "@chakra-ui/react" import { ReactElement, ReactNode, cloneElement } from "react" type TIconTagProps = Readonly<{ icon: ReactElement label: string info?: ReactNode }> & Pick & TagProps export function IconTag({ icon: iconProps, label, info, onClick, ...tagProps }: TIconTagProps) { const icon = cloneElement(iconProps, { boxSize: 4 }) return ( {icon} {label} ) } ================================================ FILE: desktop/src/components/Tag/index.ts ================================================ export { IconTag } from "./IconTag" ================================================ FILE: desktop/src/components/Terminal/Terminal.tsx ================================================ import { Box, useColorModeValue, useToken } from "@chakra-ui/react" import { css } from "@emotion/react" import React, { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, } from "react" import { ITerminalAddon, ITheme as IXTermTheme, Terminal as XTermTerminal } from "@xterm/xterm" import { FitAddon } from "@xterm/addon-fit" import { exists, remToPx } from "@/lib" type TTerminalRef = Readonly<{ clear: VoidFunction write: (data: string) => void writeln: (data: string) => void highlight: ( row: number, col: number, len: number, color: string, invertText: boolean ) => (() => void) | undefined getTerminal: () => XTermTerminal | null }> type TTerminalProps = Readonly<{ fontSize: string borderRadius?: string onResize?: (cols: number, rows: number) => void }> export type TTerminal = TTerminalRef export const Terminal = forwardRef(function T( { fontSize, onResize, borderRadius }, ref ) { const containerRef = useRef(null) const terminalRef = useRef(null) const termFitRef = useRef(null) const backgroundColorToken = useColorModeValue("gray.900", "background.darkest") const backgroundColor = useToken("colors", backgroundColorToken) const textColor = useToken("colors", "gray.100") const scrollBarThumbToken = useColorModeValue("gray.500", "gray.200") const scrollBarThumbColor = useToken("colors", scrollBarThumbToken) const selectionBackgroundToken = useColorModeValue("gray.600", "gray.600") const selectionBackgroundColor = useToken("colors", selectionBackgroundToken) const terminalTheme = useMemo>( () => ({ background: backgroundColor, foreground: textColor, selectionBackground: selectionBackgroundColor, }), [backgroundColor, selectionBackgroundColor, textColor] ) useLayoutEffect(() => { if (!exists(terminalRef.current)) { const terminal = new XTermTerminal({ convertEol: true, scrollback: 25_000, theme: terminalTheme, allowProposedApi: true, cursorStyle: "underline", disableStdin: true, cursorBlink: false, fontSize: remToPx(fontSize), }) terminalRef.current = terminal terminal.attachCustomKeyEventHandler(event => { return !(event.type === "keydown" && event.key === "c" && event.ctrlKey); }); const loadAddon = ( AddonClass: new () => T, ref: React.MutableRefObject ) => { const addon = new AddonClass() ref.current = addon terminal.loadAddon(addon) return addon } const termFit = loadAddon(FitAddon, termFitRef) // Perform initial fit. Dimensions are only available after the terminal has been rendered once. const disposable = terminal.onRender(() => { if (termFit.proposeDimensions()) { termFit.fit() disposable.dispose() } }) terminal.open(containerRef.current!) // Clean up aaaall the things :) return () => { disposable.dispose() termFitRef.current?.dispose() termFitRef.current = null terminalRef.current?.dispose() terminalRef.current = null } } // Don't initialize more than once! Use imperative api to update terminal state // eslint-disable-next-line react-hooks/exhaustive-deps }, []) // Apply outer resize handler to the terminal here. // Terminal should be guaranteed to be present by the time we get here. useEffect(() => { if (!onResize) { return } const disposable = terminalRef.current?.onResize((event) => { onResize(event.cols, event.rows) }) return () => { disposable?.dispose() } }, [onResize]) useEffect(() => { const resizeHandler = () => { try { termFitRef.current?.fit() } catch { /* ignore */ } } window.addEventListener("resize", resizeHandler, true) return () => window.removeEventListener("resize", resizeHandler, true) }, []) useEffect(() => { let maybeTheme = terminalRef.current?.options.theme if (exists(maybeTheme)) { maybeTheme = terminalTheme } }, [terminalTheme]) useEffect(() => { let maybeFontSize = terminalRef.current?.options.fontSize if (exists(maybeFontSize)) { maybeFontSize = remToPx(fontSize) } }, [fontSize]) useImperativeHandle( ref, () => { return { clear() { terminalRef.current?.clear() }, write(data) { terminalRef.current?.write(data) termFitRef.current?.fit() }, writeln(data) { terminalRef.current?.writeln(data) termFitRef.current?.fit() }, highlight(row: number, startCol: number, len: number, color: string, invertText: boolean) { const terminal = terminalRef.current if (!terminal) { return undefined } const rowRelative = -terminal.buffer.active.baseY - terminal.buffer.active.cursorY + row const marker = terminal.registerMarker(rowRelative) const decoration = terminal.registerDecoration({ marker, x: startCol, width: len, backgroundColor: color, foregroundColor: invertText ? "#000000" : "#FFFFFF", layer: "top", overviewRulerOptions: { color: color, }, }) return () => { marker.dispose() decoration?.dispose() } }, getTerminal() { return terminalRef.current }, } }, [terminalRef] ) return ( ) }) ================================================ FILE: desktop/src/components/Terminal/TerminalSearchBar.tsx ================================================ import { Box, HStack, IconButton, Input, InputGroup, InputLeftElement, InputRightElement, Tooltip, useColorModeValue, } from "@chakra-ui/react" import { ArrowDown, ArrowUp, MatchCase, Search, WholeWord } from "@/icons" import { ReactElement, useEffect, useRef, useState } from "react" import { TSearchOptions } from "@/components/Terminal/useTerminalSearch" type TTerminalSearchBarProps = { prevSearchResult: VoidFunction nextSearchResult: VoidFunction totalSearchResults: number activeSearchResult: number onUpdateSearchOptions: (searchOptions: TSearchOptions) => void paddingX?: number paddingY?: number } export function TerminalSearchBar({ prevSearchResult, nextSearchResult, totalSearchResults, activeSearchResult, onUpdateSearchOptions, paddingY, paddingX, }: TTerminalSearchBarProps) { const bgColor = useColorModeValue("white", "gray.800") const [searchString, setSearchString] = useState(undefined) const [debouncedSearchString, setDebouncedSearchString] = useState(undefined) const [caseSensitive, setCaseSensitive] = useState(false) const [wholeWordSearch, setWholeWordSearch] = useState(false) const searchInputRef = useRef(null) // Debounce to prevent stutter when having a huge amount of results. useEffect(() => { // Sneaky heuristic: // If we have more than two characters, we're likely to have a more sane amount of results, so we can skip debouncing. const len = searchString?.length ?? 0 if (len > 2) { setDebouncedSearchString(searchString) return } const timeout = setTimeout(() => { setDebouncedSearchString(searchString) }, 200) return () => clearTimeout(timeout) }, [searchString]) // Pass on the search options to the outside world. We do it like this so the debouncing works nicely. useEffect(() => { onUpdateSearchOptions({ searchString: debouncedSearchString, caseSensitive, wholeWordSearch }) }, [debouncedSearchString, wholeWordSearch, caseSensitive, onUpdateSearchOptions]) return ( searchInputRef.current?.focus()}> { if (e.key === "Enter") { if (e.shiftKey) { prevSearchResult() } else { nextSearchResult() } } }} onChange={(e) => { setSearchString(e.target.value ? e.target.value : undefined) }} /> } value={caseSensitive} setValue={setCaseSensitive} /> } value={wholeWordSearch} setValue={setWholeWordSearch} /> {totalSearchResults > 0 ? ( {activeSearchResult + 1} of {totalSearchResults} ) : searchString ? ( 0 of 0 ) : ( <> )} } /> } /> ) } function ToggleButton({ label, icon, value, setValue, }: { label: string icon: ReactElement | undefined value: boolean setValue: (value: boolean) => void }) { const hoverBgColor = useColorModeValue("gray.100", "gray.700") return ( setValue(!value)} /> ) } ================================================ FILE: desktop/src/components/Terminal/index.ts ================================================ export * from "./Terminal" export * from "./useStreamingTerminal" export * from "./TerminalSearchBar" ================================================ FILE: desktop/src/components/Terminal/useStreamingTerminal.tsx ================================================ import dayjs from "dayjs" import { Theme, useToken } from "@chakra-ui/react" import React, { useCallback, useMemo, useRef } from "react" import { TStreamEventListenerFn } from "@/client" import { TLogOutput } from "@/types" import { Terminal, TTerminal } from "./Terminal" import { TSearchOptions, useTerminalSearch } from "@/components/Terminal/useTerminalSearch" export function useStreamingTerminal({ fontSize, borderRadius, searchOptions, }: | { fontSize?: keyof Theme["fontSizes"] borderRadius?: keyof Theme["radii"] searchOptions?: TSearchOptions } | undefined = {}) { const terminalRef = useRef(null) const { internals: { searchStateRef, debounceSearchResults, resetSearch, onResize }, searchApi, } = useTerminalSearch(terminalRef, searchOptions) const fontSizeToken = useToken( "fontSizes", useMemo(() => fontSize ?? "md", [fontSize]) ) const borderRadiusToken = useToken( "radii", useMemo(() => borderRadius ?? "md", [borderRadius]) ) const terminal = useMemo( () => ( ), [fontSizeToken, onResize, borderRadiusToken] ) const connectStream = useCallback( (event) => { switch (event.type) { case "data": { if (event.data.message === undefined) { return } const formattedLine = formatLine(event.data) terminalRef.current?.writeln(formattedLine.ansi) searchStateRef.current.preWrappedLines = undefined searchStateRef.current.searchableLines.push(...processInputLine(formattedLine.plain)) debounceSearchResults(searchStateRef.current.searchOptions) break } case "error": { if (event.error.message === undefined) { return } const formattedLine = formatLine(event.error) terminalRef.current?.writeln(formattedLine.ansi) searchStateRef.current.preWrappedLines = undefined searchStateRef.current.searchableLines.push(...processInputLine(formattedLine.plain)) debounceSearchResults(searchStateRef.current.searchOptions) break } } }, [terminalRef, searchStateRef, debounceSearchResults] ) const clear = useCallback(() => { resetSearch() terminalRef.current?.clear() }, [terminalRef, resetSearch]) return { terminal, connectStream, clear, search: searchApi, } } function processInputLine(line: string) { // Default tabStopWidth is 8. const withoutTabs = line.replaceAll(/\t/g, " ".repeat(8)) const subLines = withoutTabs.split(/\r\n|\n/) return subLines.map((sl) => { const splitByCR = sl.split("\r") return splitByCR[splitByCR.length - 1]! }) } const ANSI_COLOR = { Reset: "0", White: "97", BrightCyan: "96", BrightMagenta: "95", BrightBlue: "94", BrightYellow: "93", BrightGreen: "92", BrightRed: "91", BrightBlack: "90", DarkWhite: "37", DarkCyan: "36", DarkMagenta: "35", DarkBlue: "34", DarkYellow: "33", DarkGreen: "32", DarkRed: "31", Black: "30", } const ANSI_TEXT = { Bold: "1", Underline: "4", NoUnderline: "24", Reverse: "7", NoReverse: "27", } const LOG_COLORS = { panic: ANSI_COLOR.DarkMagenta, fatal: ANSI_COLOR.DarkRed, error: ANSI_COLOR.BrightRed, warn: ANSI_COLOR.DarkYellow, info: ANSI_COLOR.BrightBlue, debug: ANSI_COLOR.BrightGreen, } function formatLine({ level, message, time }: TLogOutput) { let levelColor = ANSI_COLOR.White if (level in LOG_COLORS) { levelColor = LOG_COLORS[level as keyof typeof LOG_COLORS] } const formattedTime = dayjs(time).format("HH:mm:ss") const date = `\x1b[${ANSI_COLOR.DarkWhite}m[${formattedTime}]` const prefix = `\x1b[${ANSI_TEXT.Bold};${levelColor}m${level}` const data = `\x1b[${ANSI_COLOR.Reset}m${message}` return { ansi: `${date} ${prefix} ${data}`, plain: `[${formattedTime}] ${level} ${message}` } } ================================================ FILE: desktop/src/components/Terminal/useTerminalSearch.tsx ================================================ import React, { useCallback, useEffect, useRef, useState } from "react" import { TTerminal } from "@/components" export type TSearchOptions = { searchString?: string caseSensitive?: boolean wholeWordSearch?: boolean } type TSearchResult = [row: number, col: number, len: number] type TDisplayLine = { index: number text: string inputLine: number startCol: number endCol: number } type THighlight = { displayRow: number // Bounds of the highlight within a row. startCol: number endCol: number // Which search result this highlight is attached to. resultIndex: number } type TDisplayLineMap = { [key: number]: TDisplayLine[] } // Used for keeping track of anchors/jump points in the wrapped lines to jump to for any given search result. type TJumpMap = { [resultIndex: number]: number } type TSearchState = { searchableLines: string[] disposables: VoidFunction[] searchOptions: TSearchOptions | undefined jumpMap: TJumpMap debounce: number | undefined activeSearchResult: number preWrappedLines: TDisplayLineMap | undefined searchResults: TSearchResult[] highlights: THighlight[] } export function useTerminalSearch( terminalRef: React.MutableRefObject, searchOptions?: TSearchOptions ) { const [totalSearchResults, setTotalSearchResults] = useState(-1) const [activeSearchResult, setActiveSearchResult] = useState(0) // We have to exercise caution not to re-generate connectStream, // so we have to store a lot of state outside the usual mechanisms. // Otherwise, we will make the terminal flicker. const searchStateRef = useRef({ searchableLines: [], disposables: [], searchOptions, jumpMap: {}, debounce: undefined, activeSearchResult: 0, preWrappedLines: undefined, searchResults: [], highlights: [], }) const clearDisposables = useCallback(() => { const toClear = searchStateRef.current.disposables toClear.forEach((disposable) => disposable()) searchStateRef.current.disposables = [] }, [searchStateRef]) const repaintHighlights = useCallback( (highlights: THighlight[]) => { const terminal = terminalRef.current const displayLines = searchStateRef.current.preWrappedLines const searchResults = searchStateRef.current.searchResults if (!displayLines || !terminal || !searchResults.length) { return } searchStateRef.current.highlights = highlights for (const highlight of highlights) { const isActive = highlight.resultIndex === searchStateRef.current.activeSearchResult const disposable = terminal.highlight( highlight.displayRow, highlight.startCol, highlight.endCol - highlight.startCol, isActive ? "#E4ADFF" : "#8E00EB", isActive ) if (disposable) { searchStateRef.current.disposables.push(disposable) } } }, [searchStateRef, terminalRef] ) // When the terminal is resized, we need to re-calculate the highlights and jump anchors, // as these have to be positioned per wrapped line. const onResize = useCallback( (cols: number) => { const displayLines = wrapLines(searchStateRef.current.searchableLines, cols) searchStateRef.current.preWrappedLines = displayLines const [highlights, jumpMap] = generateHighlights( searchStateRef.current.searchResults, displayLines ) searchStateRef.current.jumpMap = jumpMap clearDisposables() repaintHighlights(highlights) }, [repaintHighlights, clearDisposables] ) // Currently we kind of have to split the state for the active search result to allow re-rendering // but also to prevent a change of the connectStream function, so this is a setter that handles both ends. const changeActiveSearchResult = useCallback( (result: number, repaint?: boolean) => { searchStateRef.current.activeSearchResult = result setActiveSearchResult(result) if (repaint) { clearDisposables() repaintHighlights(searchStateRef.current.highlights) } }, [searchStateRef, repaintHighlights, clearDisposables, setActiveSearchResult] ) const jumpToResult = useCallback( (resultIndex: number, repaint?: boolean) => { changeActiveSearchResult(resultIndex, repaint) const jumpIndex = searchStateRef.current.jumpMap[resultIndex] if (jumpIndex != null) { terminalRef.current?.getTerminal()?.scrollToLine(jumpIndex) } }, [terminalRef, searchStateRef, changeActiveSearchResult] ) const jumpNext = useCallback(() => { if (totalSearchResults <= 1) { return } const nextIndex = wrapNumber(searchStateRef.current.activeSearchResult + 1, totalSearchResults) jumpToResult(nextIndex, true) }, [totalSearchResults, jumpToResult, searchStateRef]) const jumpPrev = useCallback(() => { if (totalSearchResults <= 1) { return } const prevIndex = wrapNumber(searchStateRef.current.activeSearchResult - 1, totalSearchResults) jumpToResult(prevIndex, true) }, [totalSearchResults, jumpToResult, searchStateRef]) const performSearch = useCallback( (opts: TSearchOptions | undefined, jump?: boolean) => { clearDisposables() const terminal = terminalRef.current const inputLines = searchStateRef.current.searchableLines if (!terminal || !opts?.searchString || !inputLines.length) { setTotalSearchResults(-1) searchStateRef.current.highlights = [] searchStateRef.current.searchResults = [] return } const results = (searchStateRef.current.searchResults = search(inputLines, opts)) setTotalSearchResults(results.length) if (!results.length) { searchStateRef.current.highlights = [] searchStateRef.current.searchResults = [] return } // We need to calculate wrapped lines: // xterm internally treats lines that get wrapped as seperate lines & uses these for navigation. // Did not find a reasonable way to extract these from xterm, so we do it ourselves. let displayLines = searchStateRef.current.preWrappedLines // Optimization: Don't calculate the wrapping if we've done it before for the current set of lines. // Requires the consumer of this API to properly reset the preWrappedLines when it feeds in new lines. if (!displayLines) { searchStateRef.current.preWrappedLines = displayLines = wrapLines( inputLines, terminal.getTerminal()?.cols ?? 0 ) } searchStateRef.current.preWrappedLines = displayLines const [highlights, jumpMap] = generateHighlights(results, displayLines) searchStateRef.current.jumpMap = jumpMap if (jump) { jumpToResult(0) } repaintHighlights(highlights) }, [searchStateRef, terminalRef, jumpToResult, clearDisposables, repaintHighlights] ) // This is used for a search while the terminal still gets new input. // Since it is likely that there are going to be many new lines within a short timeframe, // we debounce the search on new input so we don't calculate results uselessly. const debounceSearchResults = useCallback( (opts: TSearchOptions | undefined) => { if (searchStateRef.current.debounce != null) { clearTimeout(searchStateRef.current.debounce) } const timeout = setTimeout(() => { if (searchStateRef.current.debounce === timeout) { searchStateRef.current.debounce = undefined } performSearch(opts) }, 100) as unknown as number searchStateRef.current.debounce = timeout }, [performSearch, searchStateRef] ) const resetSearch = useCallback(() => { // Remove all dangling highlights. clearDisposables() // Reset internal search state. searchStateRef.current = { // We keep the current search options because these are synced from the outside. searchOptions: searchStateRef.current.searchOptions, searchableLines: [], disposables: [], jumpMap: {}, debounce: undefined, activeSearchResult: 0, preWrappedLines: undefined, searchResults: [], highlights: [], } // Reset externally available state. changeActiveSearchResult(0) setTotalSearchResults(-1) }, [setTotalSearchResults, changeActiveSearchResult, searchStateRef, clearDisposables]) // Synchronize the search options with the consuming components and re-trigger the search when they change. useEffect(() => { searchStateRef.current.searchOptions = searchOptions performSearch(searchOptions, true) }, [searchOptions, performSearch]) return { internals: { searchStateRef, debounceSearchResults, resetSearch, onResize, }, searchApi: { nextSearchResult: jumpNext, prevSearchResult: jumpPrev, totalSearchResults, activeSearchResult, }, } } function search(inputLines: string[], opts: TSearchOptions) { let pattern = escapeRegex(opts.searchString!) if (opts.wholeWordSearch) { pattern = `\\b${pattern}\\b` } return inputLines.reduce((accum, line, index) => { const regex = new RegExp(pattern, opts.caseSensitive ? "g" : "ig") let match while ((match = regex.exec(line)) && match[0]) { accum.push([index, match.index, match[0].length]) } return accum }, [] as TSearchResult[]) } /** * Generates one or more highlight specification per search result. * One search result can produce multiple highlights as they can get wrapped and we have to instantiate * decorations per wrapped line. */ function generateHighlights( results: TSearchResult[], displayLinesMap: TDisplayLineMap ): [THighlight[], TJumpMap] { const highlights: THighlight[] = [] const jumpMap: TJumpMap = {} results.forEach((result, resultIndex) => { const [inputLine, start, length] = result const end = start + length const displayLines = displayLinesMap[inputLine]! displayLines.forEach((displayLine) => { const overlapStart = Math.max(start, displayLine.startCol) const overlapEnd = Math.min(end, displayLine.endCol) if (overlapStart < overlapEnd) { if (jumpMap[resultIndex] == null) { jumpMap[resultIndex] = displayLine.index } highlights.push({ displayRow: displayLine.index, startCol: overlapStart - displayLine.startCol, endCol: overlapEnd - displayLine.startCol, resultIndex, }) } }) }) return [highlights, jumpMap] } /** * Emulates the wrapping of lines by max columns as it is done in the terminal. */ function wrapLines(inputLines: string[], cols: number) { const result: TDisplayLineMap = {} let count = 0 for (let inputLineIndex = 0; inputLineIndex < inputLines.length; inputLineIndex++) { const line = inputLines[inputLineIndex]! let startCharIndex = 0 const displayLines: TDisplayLine[] = [] // Special case: There may be lines with 0 length. // While no displayLine is required for this, we still need to increment the counter. if (line.length === 0) { count++ } else { while (startCharIndex < line.length) { const endCharIndex = Math.min(startCharIndex + cols, line.length) const text = line.substring(startCharIndex, endCharIndex) displayLines.push({ index: count++, text: text, inputLine: inputLineIndex, startCol: startCharIndex, endCol: endCharIndex, }) startCharIndex = endCharIndex } } result[inputLineIndex] = displayLines } return result } /** * Wraps around numbers in the bounds [0-max]. */ function wrapNumber(num: number, max: number) { return ((num % max) + max) % max } /** * Escapes anything that could be interpreted as regex syntax when parsing the string as a regex. */ function escapeRegex(str: string) { return str.replace(/[/\-\\^$*+?.()|[\]{}]/g, "\\$&") } ================================================ FILE: desktop/src/components/Warning/WarningMessageBox.tsx ================================================ import { Box, Text, useColorModeValue } from "@chakra-ui/react" import React from "react" const SIZES = { sm: { fontSize: "sm", }, md: { fontSize: "md", }, } const VARIANTS = { solid: { color: { light: "orange.700", dark: "orange.800" }, }, ghost: { color: { light: "orange.400", dark: "orange.300" }, }, } type TWarningMessageBoxProps = Readonly<{ warning: React.ReactNode size?: keyof typeof SIZES variant?: "solid" | "ghost" }> export function WarningMessageBox({ warning, size = "md", variant = "solid", }: TWarningMessageBoxProps) { const { color } = VARIANTS[variant] const backgroundColor = useColorModeValue("orange.100", "orange.200") const textColor = useColorModeValue(color.light, color.dark) const { fontSize } = SIZES[size] return ( {warning} ) } ================================================ FILE: desktop/src/components/Warning/index.ts ================================================ export { WarningMessageBox } from "./WarningMessageBox" ================================================ FILE: desktop/src/components/WorkspaceOwnerFilter/WorkspaceOwnerFilter.tsx ================================================ import { ChevronDownIcon } from "@chakra-ui/icons" import { Button, Menu, MenuButton, MenuItemOption, MenuList, MenuOptionGroup, } from "@chakra-ui/react" import { useCallback } from "react" export type TWorkspaceOwnerFilterState = "self" | "all" export function WorkspaceOwnerFilter({ ownerFilter, setOwnerFilter, }: { ownerFilter: TWorkspaceOwnerFilterState setOwnerFilter: (ownerFilter: TWorkspaceOwnerFilterState) => void }) { const onChange = useCallback( (value: string[] | string) => { setOwnerFilter((Array.isArray(value) ? value[0] : value) as TWorkspaceOwnerFilterState) }, [setOwnerFilter] ) return ( }> Workspaces: {ownerFilter == "self" ? "Mine" : "All"} Mine All ) } ================================================ FILE: desktop/src/components/WorkspaceOwnerFilter/index.ts ================================================ export * from "./WorkspaceOwnerFilter" ================================================ FILE: desktop/src/components/WorkspaceSorter/WorkspaceSorter.tsx ================================================ import { Button, Menu, MenuButton, MenuItemOption, MenuList, MenuOptionGroup, } from "@chakra-ui/react" import { ChevronDownIcon } from "@chakra-ui/icons" import { DEFAULT_SORT_WORKSPACE_MODE, ESortWorkspaceMode } from "@/lib/useSortWorkspaces" import { useCallback } from "react" export function WorkspaceSorter({ sortMode, setSortMode, }: { sortMode: ESortWorkspaceMode setSortMode: (sortMode: ESortWorkspaceMode) => void }) { const onChange = useCallback( (value: string | string[] | undefined) => { const mode = Array.isArray(value) ? (value[0] as ESortWorkspaceMode | undefined) : (value as ESortWorkspaceMode | undefined) setSortMode(mode ?? DEFAULT_SORT_WORKSPACE_MODE) }, [setSortMode] ) return ( }> Sort by: {sortMode} {Object.values(ESortWorkspaceMode).map((option) => ( {option} ))} ) } ================================================ FILE: desktop/src/components/WorkspaceSorter/index.ts ================================================ export * from "./WorkspaceSorter" ================================================ FILE: desktop/src/components/WorkspaceStatusFilter/WorkspaceStatusFilter.tsx ================================================ import { Button, HStack, Menu, MenuButton, MenuDivider, MenuItemOption, MenuList, MenuOptionGroup, Text, } from "@chakra-ui/react" import { WorkspaceStatus } from "@/icons" import { WORKSPACE_STATUSES } from "@/constants" import { WorkspaceStatusBadge } from "@/views/Workspaces/WorkspaceStatusBadge" import { useCallback } from "react" import { WorkspaceDisplayStatusBadge } from "@/views/Pro/Workspace/WorkspaceStatus" import { TWorkspace } from "@/types" import { TWorkspaceDisplayStatus, WorkspaceDisplayStatus } from "@/views/Pro/Workspace/status" export type TWorkspaceStatusFilterState = string[] | "all" export function WorkspaceStatusFilter({ statusFilter, setStatusFilter, variant = "oss", }: { statusFilter: TWorkspaceStatusFilterState setStatusFilter: (statusFilter: TWorkspaceStatusFilterState) => void variant?: "oss" | "pro" }) { const availableStatuses = variant === "oss" ? WORKSPACE_STATUSES : Object.values(WorkspaceDisplayStatus) const onSelectAll = useCallback(() => { if (statusFilter === "all") { setStatusFilter([]) } else { setStatusFilter("all") } }, [statusFilter, setStatusFilter]) const onChange = useCallback( (value: string | string[]) => { setStatusFilter(typeof value === "string" ? [value] : value) }, [setStatusFilter] ) return ( }> Status ({getCurrentFilterCount(statusFilter, availableStatuses.length)}/ {availableStatuses.length}) Select All {availableStatuses.map((status) => ( {variant === "oss" ? ( ) : ( )}{" "} {status || "Waiting to Initialize"} ))} ) } function getCurrentFilterCount(filter: TWorkspaceStatusFilterState, total: number) { if (filter === "all") { return total } return filter.length } ================================================ FILE: desktop/src/components/WorkspaceStatusFilter/index.ts ================================================ export * from "./WorkspaceStatusFilter" ================================================ FILE: desktop/src/components/index.ts ================================================ export * from "./Animation" export * from "./AutoComplete" export * from "./CardHeader" export * from "./Error" export * from "./Form" export * from "./IDEGroup" export * from "./IDEIcon" export * from "./ListSelection" export * from "./DeleteWorkspacesModal" export * from "./ExampleCard" export * from "./Layout" export * from "./LoftOSSBadge" export * from "./BottomActionBar" export * from "./Section" export * from "./Steps" export * from "./Terminal" export * from "./Tag" export * from "./useInstallCLI" export * from "./Warning" export * from "./WorkspaceSorter" export * from "./WorkspaceStatusFilter" export * from "./WorkspaceOwnerFilter" ================================================ FILE: desktop/src/components/useInstallCLI.tsx ================================================ import { QuestionIcon } from "@chakra-ui/icons" import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, Button, ButtonGroup, Code, Tooltip, useDisclosure, } from "@chakra-ui/react" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { useMemo, useRef } from "react" import { client } from "../client" import { CheckCircle, ExclamationCircle } from "../icons" import { Err, Failed, isError, isMacOS, isWindows } from "../lib" import { QueryKeys } from "../queryKeys" import { ErrorMessageBox } from "./Error" export function useInstallCLI() { const { isOpen, onOpen: showAlertDialog, onClose } = useDisclosure() const cancelRef = useRef(null) const { data: isCLIInstalled } = useQuery({ queryKey: QueryKeys.IS_CLI_INSTALLED, queryFn: async () => { return (await client.isCLIInstalled()).unwrap()! }, }) const queryClient = useQueryClient() const { mutate: addBinaryToPath, isLoading, error, status, } = useMutation, { force?: boolean }>({ mutationFn: async ({ force = false }) => { ;(await client.installCLI(force)).unwrap() // throw Return.Failed("Did not work") }, onSettled: () => { queryClient.invalidateQueries(QueryKeys.IS_CLI_INSTALLED) }, onError: (_, { force }) => { if (isMacOS && !force) { showAlertDialog() } }, }) const badge = useMemo(() => { if (isCLIInstalled === undefined) { return ( ) } return isCLIInstalled ? ( ) : ( ) }, [isCLIInstalled]) const button = useMemo(() => { return ( <> Failed to add CLI to path Do you want to retry with Admin Privileges? You will be prompted for authentication ) }, [addBinaryToPath, isLoading, isOpen, onClose, status]) const helpText = useMemo(() => { return ( <> Adds the DevPod CLI to your $PATH.{" "} {isWindows ? ( <> It will be placed in %APP_DATA%\sh.loft.devpod\bin ) : ( <> It will be placed in either /usr/local/bin,$HOME/.local/bin or{" "} $HOME/bin depending on your permissions )} ) }, []) const errorMessage = useMemo(() => { return error !== null && isError(error.val) && }, [error]) return { isInstalled: isCLIInstalled, install: addBinaryToPath, isLoading, error, status, badge, button, helpText, errorMessage, } } ================================================ FILE: desktop/src/constants.ts ================================================ import { BoxProps } from "@chakra-ui/react" import { AWSSvg, AWSWhiteSvg, AzureSvg, CivoSvg, DigitalOceanSvg, DockerSvg, GCloudSvg, KubernetesSvg, SSHSvg, } from "./images" export const STATUS_BAR_HEIGHT: NonNullable = "8" export const SIDEBAR_WIDTH: BoxProps["width"] = "15rem" export const RECOMMENDED_PROVIDER_SOURCES = [ // generic { image: DockerSvg, imageDarkMode: undefined, name: "docker", group: "generic" }, { image: KubernetesSvg, imageDarkMode: undefined, name: "kubernetes", group: "generic" }, { image: SSHSvg, imageDarkMode: undefined, name: "ssh", group: "generic" }, // cloud { image: AWSSvg, imageDarkMode: AWSWhiteSvg, name: "aws", group: "cloud" }, { image: GCloudSvg, imageDarkMode: undefined, name: "gcloud", group: "cloud" }, { image: AzureSvg, imageDarkMode: undefined, name: "azure", group: "cloud" }, { image: DigitalOceanSvg, imageDarkMode: undefined, name: "digitalocean", group: "cloud" }, { image: CivoSvg, imageDarkMode: undefined, name: "civo", group: "cloud" }, ] as const export const WORKSPACE_SOURCE_BRANCH_DELIMITER = "@" export const WORKSPACE_SOURCE_COMMIT_DELIMITER = "@sha256:" export const WORKSPACE_STATUSES = ["Running", "Stopped", "Busy", "NotFound"] as const ================================================ FILE: desktop/src/contexts/DevPodContext/DevPodProvider/DevPodContext.tsx ================================================ import { createContext } from "react" import { TProviders, TQueryResult } from "../../../types" export type TDevpodContext = Readonly<{ providers: TQueryResult }> export const DevPodContext = createContext(null) ================================================ FILE: desktop/src/contexts/DevPodContext/DevPodProvider/DevPodProvider.tsx ================================================ import { useQuery } from "@tanstack/react-query" import { ReactNode, useMemo } from "react" import { client } from "../../../client" import { QueryKeys } from "../../../queryKeys" import { REFETCH_PROVIDER_INTERVAL_MS } from "../constants" import { usePollWorkspaces } from "../workspaces" import { DevPodContext, TDevpodContext } from "./DevPodContext" export function DevPodProvider({ children }: Readonly<{ children?: ReactNode }>) { usePollWorkspaces() const providersQuery = useQuery({ queryKey: QueryKeys.PROVIDERS, queryFn: async () => (await client.providers.listAll()).unwrap(), refetchInterval: REFETCH_PROVIDER_INTERVAL_MS, enabled: true, }) const value = useMemo( () => ({ providers: [ providersQuery.data, { status: providersQuery.status, error: providersQuery.error }, ], }), [providersQuery.data, providersQuery.status, providersQuery.error] ) return {children} } export function ProviderProvider({ children }: Readonly<{ children?: ReactNode }>) { const providersQuery = useQuery({ queryKey: QueryKeys.PROVIDERS, queryFn: async () => (await client.providers.listAll()).unwrap(), refetchInterval: REFETCH_PROVIDER_INTERVAL_MS, enabled: true, }) const value = useMemo( () => ({ providers: [ providersQuery.data, { status: providersQuery.status, error: providersQuery.error }, ], }), [providersQuery.data, providersQuery.status, providersQuery.error] ) return {children} } ================================================ FILE: desktop/src/contexts/DevPodContext/DevPodProvider/index.ts ================================================ export { DevPodProvider, ProviderProvider } from "./DevPodProvider" export { DevPodContext } from "./DevPodContext" export type { TDevpodContext } from "./DevPodContext" ================================================ FILE: desktop/src/contexts/DevPodContext/Pro/ContextSwitcher.tsx ================================================ import { Close, Connect, DevpodWordmark, Ellipsis, Folder } from "@/icons" import { Result, getDisplayName, useLoginProModal } from "@/lib" import { Routes } from "@/routes" import { TProInstance } from "@/types" import { useDeleteProviderModal } from "@/views/Providers" import { ArrowUpDownIcon, CheckIcon } from "@chakra-ui/icons" import { Box, Button, HStack, Heading, IconButton, Image, List, ListItem, Menu, MenuButton, MenuItem, MenuList, Popover, PopoverBody, PopoverContent, PopoverTrigger, Portal, Spinner, Text, Tooltip, VStack, useColorModeValue, } from "@chakra-ui/react" import { ManagementV1Project } from "@loft-enterprise/client/gen/models/managementV1Project" import { ReactNode, useMemo } from "react" import { useNavigate } from "react-router" import { useProInstances } from "../proInstances" import { HOST_OSS } from "./constants" type THostPickerProps = Readonly<{ currentHost: string onHostChange: (newHost: string) => void currentProject: ManagementV1Project projects: readonly ManagementV1Project[] onProjectChange: (newProject: ManagementV1Project) => void onCancelWatch?: () => Promise> waitingForCancel: boolean }> export function ContextSwitcher({ currentHost, projects, currentProject, onProjectChange, onHostChange, onCancelWatch, waitingForCancel, }: THostPickerProps) { const [[rawProInstances]] = useProInstances() const proInstances = useMemo(() => { const p: (TProInstance & { image?: string | ReactNode })[] = rawProInstances ?.slice() .sort((a, b) => { if (a.host === currentHost) { return -1 } if (b.host === currentHost) { return 1 } return 0 }) .map((proInstance) => ({ ...proInstance })) ?? [] p.push({ host: HOST_OSS, image: , authenticated: undefined, provider: undefined, creationTimestamp: undefined, capabilities: undefined, }) return p }, [currentHost, rawProInstances]) const { modal: loginProModal, handleOpenLogin: handleConnectClicked } = useLoginProModal() const handleConnectPlatform = () => { handleConnectClicked() } const hoverBgColor = useColorModeValue("gray.100", "gray.700") const projectsColor = useColorModeValue("gray.600", "gray.300") return ( <> {waitingForCancel ? ( ) : ( {proInstances.map(({ host, authenticated, image }, index) => ( onHostChange(host!)} /> {host === currentHost && ( Projects {projects.map((project) => ( ))} )} ))} )} {loginProModal} ) } type TPlatformDetailsProps = Readonly<{ host: string currentHost: string image: ReactNode authenticated?: boolean | null showBorder?: boolean onClick: VoidFunction onConnect: VoidFunction onCancelWatch?: () => Promise> }> function PlatformDetails({ host, currentHost, image, authenticated, showBorder = true, onClick, onConnect, onCancelWatch, }: TPlatformDetailsProps) { const navigate = useNavigate() const [, { disconnect }] = useProInstances() const { modal: deleteProviderModal, open: openDeleteProviderModal } = useDeleteProviderModal( host, "Pro instance", "disconnect", async () => { await onCancelWatch?.() disconnect.run({ id: host }) navigate(Routes.ROOT) } ) const hoverBgColor = useColorModeValue("gray.100", "gray.700") const menuColor = useColorModeValue("gray.700", "gray.200") return ( <> {image ? ( typeof image === "string" ? ( ) : ( image ) ) : ( {host} )} {authenticated != null && ( )} {host} {host !== HOST_OSS && ( e.stopPropagation()} as={IconButton} variant="ghost" aria-label="More actions" colorScheme="gray" icon={} /> e.stopPropagation()}> } onClick={onConnect}> Connect another platform } onClick={openDeleteProviderModal}> Disconnect )} {deleteProviderModal} ) } ================================================ FILE: desktop/src/contexts/DevPodContext/Pro/ProProvider.tsx ================================================ import { client as globalClient } from "@/client" import { DaemonClient } from "@/client/pro/client" import { TWorkspaceOwnerFilterState, ToolbarActions, ToolbarTitle } from "@/components" import { Annotations, Result } from "@/lib" import { Routes } from "@/routes" import { Text } from "@chakra-ui/react" import { ManagementV1Project } from "@loft-enterprise/client/gen/models/managementV1Project" import { useQuery } from "@tanstack/react-query" import { ReactNode, useEffect, useMemo, useState } from "react" import { Navigate, useNavigate } from "react-router-dom" import { useProInstances } from "../proInstances" import { ProWorkspaceStore, useWorkspaceStore } from "../workspaceStore" import { ContextSwitcher } from "./ContextSwitcher" import { HOST_OSS } from "./constants" import { ProContext, TProContext } from "./useProContext" export function ProProvider({ host, children }: { host: string; children: ReactNode }) { const [[proInstances, { status: proInstancesStatus }]] = useProInstances() const [isLoadingWorkspaces, setIsLoadingWorkspaces] = useState(false) const [ownerFilter, setOwnerFilter] = useState("self") const [currentProjectName, setCurrentProjectName] = useState( () => localStorage.getItem(getProjectStorageKey(host)) ?? undefined ) const navigate = useNavigate() const currentProInstance = useMemo(() => { return proInstances?.find((instance) => instance.host == host) }, [host, proInstances]) const { store } = useWorkspaceStore() const client = useMemo(() => { if (!currentProInstance) { return null } return globalClient.getProClient(currentProInstance) }, [currentProInstance]) const managementSelfQuery = useQuery({ queryKey: ["managementSelf", client], queryFn: async () => { return (await client!.getSelf()).unwrap() }, enabled: !!client, }) const projectsQuery = useQuery({ queryKey: ["pro", host, "projects", client], queryFn: async () => { return (await client!.listProjects()).unwrap() }, enabled: !!client, }) const currentProject = useMemo(() => { if (projectsQuery.data == null) { return undefined } if (!currentProjectName) { return projectsQuery.data[0] ?? undefined } const maybeProject = projectsQuery.data.find((project) => project.metadata?.name === currentProjectName) ?? undefined if (!maybeProject) { return projectsQuery.data[0] ?? undefined } return maybeProject }, [currentProjectName, projectsQuery.data]) const [cancelWatch, setCancelWatch] = useState< { fn: () => Promise> } | undefined >(undefined) const [waitingForCancel, setWaitingForCancel] = useState(false) useEffect(() => { if (!currentProject?.metadata?.name || !client) { return } setIsLoadingWorkspaces(true) if (client instanceof DaemonClient) { // daemon client impl return client.watchWorkspaces(currentProject.metadata.name, ownerFilter, (workspaces) => { // sort by last activity (newest > oldest) const sorted = workspaces.slice().sort((a, b) => { const lastActivityA = a.metadata?.annotations?.[Annotations.SleepModeLastActivity] const lastActivityB = b.metadata?.annotations?.[Annotations.SleepModeLastActivity] if (!(lastActivityA && lastActivityB)) { return 0 } return parseInt(lastActivityB, 10) - parseInt(lastActivityA, 10) }) store.setWorkspaces(sorted) setIsLoadingWorkspaces(false) }) } else { let canceled = false // proxy client impl const toCancel = client.watchWorkspacesProxy( currentProject.metadata.name, ownerFilter, (workspaces) => { if (canceled) { return } // sort by last activity (newest > oldest) const sorted = workspaces.slice().sort((a, b) => { const lastActivityA = a.metadata?.annotations?.[Annotations.SleepModeLastActivity] const lastActivityB = b.metadata?.annotations?.[Annotations.SleepModeLastActivity] if (!(lastActivityA && lastActivityB)) { return 0 } return parseInt(lastActivityB, 10) - parseInt(lastActivityA, 10) }) store.setWorkspaces(sorted) // dirty, dirty setTimeout(() => { setIsLoadingWorkspaces(false) }, 1_000) } ) function canceler() { canceled = true setCancelWatch(undefined) setWaitingForCancel(true) return toCancel().finally(() => setWaitingForCancel(false)) } setCancelWatch({ fn: canceler }) return () => { canceler() } } }, [client, store, currentProject, ownerFilter]) const handleProjectChanged = (newProject: ManagementV1Project) => { const projectName = newProject.metadata?.name ?? "" localStorage.setItem(getProjectStorageKey(host), projectName) setCurrentProjectName(projectName) navigate(Routes.toProInstance(host)) } const handleHostChanged = (newHost: string) => { if (newHost === HOST_OSS) { navigate(Routes.WORKSPACES) return } const projectName = localStorage.getItem(getProjectStorageKey(newHost)) ?? undefined setCurrentProjectName(projectName) navigate(Routes.toProInstance(newHost)) } const value = useMemo(() => { return { managementSelfQuery, currentProject, host, client: client!, isLoadingWorkspaces, ownerFilter, setOwnerFilter, } }, [managementSelfQuery, currentProject, host, client, isLoadingWorkspaces, ownerFilter]) // this pro instance doesn't exist, let's route back to root if (proInstancesStatus == "success" && !currentProInstance) { return } if (!client) { return null } return ( {host} {children} ) } const PROJECT_STORAGE_KEY = "devpod_current_project" function getProjectStorageKey(host: string) { return `${PROJECT_STORAGE_KEY}_${host}` } ================================================ FILE: desktop/src/contexts/DevPodContext/Pro/constants.ts ================================================ export const HOST_OSS = "Open Source" ================================================ FILE: desktop/src/contexts/DevPodContext/Pro/index.ts ================================================ export { ProProvider } from "./ProProvider" export { useProContext } from "./useProContext" export { ProWorkspaceInstance } from "./workspaceInstance" export { useProHost } from "./useProHost" export { useTemplates } from "./useTemplates" export { useProjectClusters } from "./useProjectClusters" ================================================ FILE: desktop/src/contexts/DevPodContext/Pro/useProContext.ts ================================================ import { ProClient } from "@/client" import { TWorkspaceOwnerFilterState } from "@/components" import { ManagementV1Project } from "@loft-enterprise/client/gen/models/managementV1Project" import { ManagementV1Self } from "@loft-enterprise/client/gen/models/managementV1Self" import { UseQueryResult } from "@tanstack/react-query" import { Dispatch, SetStateAction, createContext, useContext } from "react" export type TProContext = Readonly<{ managementSelfQuery: UseQueryResult currentProject?: ManagementV1Project host: string client: ProClient isLoadingWorkspaces: boolean ownerFilter: TWorkspaceOwnerFilterState setOwnerFilter: Dispatch> }> export const ProContext = createContext(null!) export function useProContext() { return useContext(ProContext) } ================================================ FILE: desktop/src/contexts/DevPodContext/Pro/useProHost.ts ================================================ import { useParams } from "react-router-dom" export function useProHost() { const { host } = useParams<{ host: string | undefined }>() return host } ================================================ FILE: desktop/src/contexts/DevPodContext/Pro/useProjectClusters.tsx ================================================ import { useProContext } from "@/contexts" import { QueryKeys } from "@/queryKeys" import { ManagementV1Runner } from "@/runner" import { ManagementV1ProjectClusters } from "@loft-enterprise/client/gen/models/managementV1ProjectClusters" import { useQuery, UseQueryResult } from "@tanstack/react-query" export type TProjectCluster = ManagementV1ProjectClusters & { runners?: Array } export function useProjectClusters(): UseQueryResult { const { host, currentProject, client } = useProContext() const query = useQuery({ queryKey: QueryKeys.proClusters(host, currentProject?.metadata!.name!), queryFn: async () => { return (await client.getProjectClusters(currentProject?.metadata!.name!)).unwrap() }, enabled: !!currentProject, }) return query } ================================================ FILE: desktop/src/contexts/DevPodContext/Pro/useTemplates.ts ================================================ import { useProContext } from "@/contexts" import { useQuery, UseQueryResult } from "@tanstack/react-query" import { QueryKeys } from "@/queryKeys" import { ManagementV1DevPodWorkspaceTemplate } from "@loft-enterprise/client/gen/models/managementV1DevPodWorkspaceTemplate" import { ManagementV1DevPodEnvironmentTemplate } from "@loft-enterprise/client/gen/models/managementV1DevPodEnvironmentTemplate" import { ManagementV1DevPodWorkspacePreset } from "@loft-enterprise/client/gen/models/managementV1DevPodWorkspacePreset" type TTemplates = Readonly<{ default: ManagementV1DevPodWorkspaceTemplate | undefined workspace: readonly ManagementV1DevPodWorkspaceTemplate[] environment: readonly ManagementV1DevPodEnvironmentTemplate[] presets: readonly ManagementV1DevPodWorkspacePreset[] }> export function useTemplates(): UseQueryResult { const { host, currentProject, client } = useProContext() const query = useQuery({ queryKey: QueryKeys.proWorkspaceTemplates(host, currentProject?.metadata!.name!), queryFn: async () => { const projectTemplates = ( await client.getProjectTemplates(currentProject?.metadata!.name!) ).unwrap() // try to find default template in list let defaultTemplate: ManagementV1DevPodWorkspaceTemplate | undefined = undefined if (projectTemplates?.defaultDevPodWorkspaceTemplate) { defaultTemplate = projectTemplates.devPodWorkspaceTemplates?.find( (template) => template.metadata?.name === projectTemplates.defaultDevPodWorkspaceTemplate ) } return { default: defaultTemplate, workspace: projectTemplates?.devPodWorkspaceTemplates ?? [], environment: projectTemplates?.devPodEnvironmentTemplates ?? [], presets: projectTemplates?.devPodWorkspacePresets ?? [], } }, enabled: !!currentProject, }) return query } ================================================ FILE: desktop/src/contexts/DevPodContext/Pro/workspaceInstance.ts ================================================ import { TIDE, TIdentifiable, TWorkspaceSource } from "@/types" import { ManagementV1DevPodWorkspaceInstance } from "@loft-enterprise/client/gen/models/managementV1DevPodWorkspaceInstance" import { Labels, deepCopy } from "@/lib" import { Resources } from "@loft-enterprise/client" import { ManagementV1DevPodWorkspaceInstanceStatus } from "@loft-enterprise/client/gen/models/managementV1DevPodWorkspaceInstanceStatus" export class ProWorkspaceInstance extends ManagementV1DevPodWorkspaceInstance implements TIdentifiable { public readonly status: ProWorkspaceInstanceStatus | undefined public get id(): string { const maybeID = this.metadata?.labels?.[Labels.WorkspaceID] if (!maybeID) { // If we don't have an ID we should ignore the instance. // Throwing an error for now to see how often this happens throw new Error(`No Workspace ID label present on instance ${this.metadata?.name}`) } return maybeID } constructor(instance: ManagementV1DevPodWorkspaceInstance) { super() this.apiVersion = `${Resources.ManagementV1DevPodWorkspaceInstance.group}/${Resources.ManagementV1DevPodWorkspaceInstance.version}` this.kind = Resources.ManagementV1DevPodWorkspaceInstance.kind this.metadata = deepCopy(instance.metadata) this.spec = deepCopy(instance.spec) this.status = deepCopy(instance.status) as ProWorkspaceInstanceStatus } } class ProWorkspaceInstanceStatus extends ManagementV1DevPodWorkspaceInstanceStatus { "source"?: TWorkspaceSource "ide"?: TIDE "metrics"?: ProWorkspaceMetricsSummary constructor() { super() } } class ProWorkspaceMetricsSummary { "latencyMs"?: number "connectionType"?: "direct" | "DERP" "derpRegion"?: string } ================================================ FILE: desktop/src/contexts/DevPodContext/action/action.ts ================================================ import { Result, SingleEventManager, EventManager } from "../../../lib" import { v4 as uuidv4 } from "uuid" export type TActionName = "start" | "stop" | "rebuild" | "reset" | "remove" | "checkStatus" export type TActionFn = (context: TActionContext) => Promise> export type TActionStatus = "pending" | "success" | "error" | "cancelled" export type TActionID = Action["id"] // We don't want to expose the methods to consumers of these actions, so we'll limit the type to data-only properties export type TActionObj = Pick< Action, "id" | "name" | "status" | "error" | "createdAt" | "finishedAt" | "targetID" > export type TActions = Readonly<{ active: readonly TActionObj[] history: readonly TActionObj[] }> type TActionContext = Readonly<{ id: Action["id"] }> export class Action { private _status: TActionStatus = "pending" private _error: Error | undefined = undefined private _finishedAt: number | undefined = undefined private readonly eventManager = new SingleEventManager() public readonly id = uuidv4() public readonly createdAt = Date.now() public static deserialize(str: string): TActionObj { return JSON.parse(str) } constructor( public readonly name: TActionName, public readonly targetID: string, private actionFn: TActionFn ) {} public get status() { return this._status } public get error() { return this._error } public get finishedAt() { return this._finishedAt } private failed(error: Error) { if (this._status !== "pending") { return } this._status = "error" this._error = error this._finishedAt = Date.now() this.eventManager.publish(this._status) } private succeeded() { if (this._status !== "pending") { return } this._status = "success" this._finishedAt = Date.now() this.eventManager.publish(this._status) } public run() { this.actionFn({ id: this.id }).then((result) => { if (result.err) { this.failed(result.val) return } this.succeeded() }) } public cancel() { if (this._status !== "pending") { return } // We're no longer interested in status updates this.eventManager.clear() this._status = "cancelled" this._finishedAt = Date.now() } public once(listener: (status: TActionStatus) => void): void { const unsubscribe = this.eventManager.subscribe( EventManager.toHandler((status) => { listener(status) unsubscribe() }) ) } public getData(): TActionObj { return { id: this.id, targetID: this.targetID, name: this.name, status: this.status, error: this.error, createdAt: this.createdAt, finishedAt: this.finishedAt, } } } ================================================ FILE: desktop/src/contexts/DevPodContext/action/actionHistory.ts ================================================ import { Action, TActionObj, TActions } from "./action" const HISTORY_KEY = "devpod-workspace-action-history" const MAX_HISTORY_ENTRIES = 50 export class ActionHistory { private active = new Map() private history: TActionObj[] private localStorageKey: string constructor(keySuffix?: string) { let localStorageKey = HISTORY_KEY if (keySuffix) { localStorageKey = `${localStorageKey}-${keySuffix}` } this.localStorageKey = localStorageKey const maybeHistory = localStorage.getItem(this.localStorageKey) if (maybeHistory === null) { this.history = [] return } this.history = JSON.parse(maybeHistory) as TActionObj[] } private getAllActive(): readonly TActionObj[] { const active = [] for (const action of this.active.values()) { active.push(action.getData()) } return active } public getActive(targetID: string): Action | undefined { return this.active.get(targetID) } public getAll(): TActions { const active = this.getAllActive() const history = this.history.slice() return { active, history } } public addActive(targetID: string, action: Action): void { this.active.set(targetID, action) } public archive(action: Action): void { this.active.delete(action.targetID) this.history.push(action.getData()) // Limit history size const overflow = this.history.length - MAX_HISTORY_ENTRIES if (overflow > 0) { this.history.splice(0, overflow) } window.localStorage.setItem(this.localStorageKey, JSON.stringify(this.history)) } } ================================================ FILE: desktop/src/contexts/DevPodContext/action/index.ts ================================================ export { getAction, useAction, useReplayAction, useConnectAction } from "./useAction" export { Action } from "./action" export type { TActionName, TActionObj, TActionID, TActions as TWorkspaceActions, TActionFn, } from "./action" ================================================ FILE: desktop/src/contexts/DevPodContext/action/useAction.ts ================================================ import { useCallback, useEffect, useId, useMemo, useRef, useSyncExternalStore } from "react" import { TStreamEventListenerFn, client } from "../../../client" import { TStreamID, TUnsubscribeFn } from "../../../types" import { useWorkspaceStore } from "../workspaceStore" import { IWorkspaceStore } from "../workspaceStore/workspaceStore" import { TActionID, TActionObj } from "./action" type TActionResult = Readonly<{ data: TActionObj connectOrReplay(onStream: TStreamEventListenerFn): void | VoidFunction cancel(): void }> export function useAction(actionID: TActionID | undefined): TActionResult | undefined { const { store } = useWorkspaceStore() const isCancellingRef = useRef(false) const viewID = useId() const data = useSyncExternalStore( useCallback((listener) => store.subscribe(listener), [store]), () => { if (actionID === undefined) { return undefined } return getAction(actionID, store) } ) const connect = useConnectAction(data, viewID) const replay = useReplayAction() return useMemo(() => { if (data === undefined) { return undefined } return { data, connectOrReplay: (onStream) => { if (data.status === "pending") { return connect(onStream) } return replay(data.id, onStream) }, cancel: () => { if (isCancellingRef.current) { return } isCancellingRef.current = true // could improve by setting timeout as fallback if promise doesn't resolve, let's see if this is enough client.workspaces.cancelAction(data.targetID).finally(() => { isCancellingRef.current = false }) }, } }, [data, connect, replay]) } export function getAction( actionID: TActionID, store: IWorkspaceStore ): TActionObj | undefined { const { active, history } = store.getAllActions() return [...active, ...history].find((action) => action.id === actionID) } export function useConnectAction( action: TActionObj | undefined, streamID: TStreamID ): (onStream: TStreamEventListenerFn) => void { const subscriptionRef = useRef() // Make sure we unsubscribe on onmount useEffect(() => { return () => subscriptionRef.current?.() }, []) // Unsubscribe whenever action changes useEffect(() => { if ( (action === undefined || action.status !== "pending") && subscriptionRef.current !== undefined ) { subscriptionRef.current() subscriptionRef.current = undefined } }, [action]) return useCallback( (onStream) => { if (action === undefined) { return } subscriptionRef.current = client.workspaces.subscribe(action, streamID, onStream) }, [action, streamID] ) } export function useReplayAction(): ( actionID: TActionID, onStream: TStreamEventListenerFn ) => VoidFunction { return useCallback((actionID, onStream) => client.workspaces.replayAction(actionID, onStream), []) } ================================================ FILE: desktop/src/contexts/DevPodContext/constants.ts ================================================ export const REFETCH_INTERVAL_MS = 5_000 export const REFETCH_PROVIDER_INTERVAL_MS = 1_000 ================================================ FILE: desktop/src/contexts/DevPodContext/helpers.ts ================================================ // copied from https://github.com/TkDodo/react-query/blob/c1ae82ba188fd5abda5e256cac070145e5941447/src/core/utils.ts#L346 /** * This function returns `a` if `b` is deeply equal. * If not, it will replace any deeply equal children of `b` with those of `a`. * This can be used for structural sharing between JSON values for example. */ export function replaceEqualDeep(a: unknown, b: T): T export function replaceEqualDeep(a: any, b: any): any { if (a === b) { return a } const array = Array.isArray(a) && Array.isArray(b) if (array || (isPlainObject(a) && isPlainObject(b))) { const aSize = array ? a.length : Object.keys(a).length const bItems = array ? b : Object.keys(b) const bSize = bItems.length const copy: any = array ? [] : {} let equalItems = 0 for (let i = 0; i < bSize; i++) { const key = array ? i : bItems[i] copy[key] = replaceEqualDeep(a[key], b[key]) if (copy[key] === a[key]) { equalItems++ } } const sameSize = aSize === bSize const sameItems = equalItems === aSize const keepA = sameSize && sameItems return keepA ? a : copy } return b } // Copied from: https://github.com/jonschlinkert/is-plain-object export function isPlainObject(o: any): o is Object { if (!hasObjectPrototype(o)) { return false } // If has modified constructor const ctor = o.constructor if (typeof ctor === "undefined") { return true } // If has modified prototype const prot = ctor.prototype if (!hasObjectPrototype(prot)) { return false } // If constructor does not have an Object-specific method // eslint-disable-next-line no-prototype-builtins if (!prot.hasOwnProperty("isPrototypeOf")) { return false } // Most likely a plain Object return true } function hasObjectPrototype(o: any): boolean { return Object.prototype.toString.call(o) === "[object Object]" } ================================================ FILE: desktop/src/contexts/DevPodContext/index.ts ================================================ export { getAction, useAction } from "./action" export type { TActionName, TActionID, TActionObj } from "./action" export { DevPodProvider } from "./DevPodProvider" export { useProInstances, ProInstancesProvider, useProInstanceManager } from "./proInstances" export { useProvider } from "./useProvider" export { useProviders } from "./useProviders" export { useProviderManager } from "./useProviderManager" export { useWorkspace, useWorkspaces, useAllWorkspaceActions, useWorkspaceActions, startWorkspaceAction, } from "./workspaces" export { WorkspaceStoreProvider, useWorkspaceStore, WorkspaceStore, ProWorkspaceStore, } from "./workspaceStore" export { useProHost, ProProvider, ProWorkspaceInstance, useProContext, useProjectClusters, useTemplates, } from "./Pro" ================================================ FILE: desktop/src/contexts/DevPodContext/proInstances/ProInstancesProvider.tsx ================================================ import { useQuery } from "@tanstack/react-query" import { ReactNode, useMemo } from "react" import { client } from "../../../client" import { QueryKeys } from "../../../queryKeys" import { useChangeSettings } from "../../SettingsContext" import { REFETCH_INTERVAL_MS } from "../constants" import { ProInstancesContext, TProInstancesContext } from "./context" export function ProInstancesProvider({ children }: Readonly<{ children?: ReactNode }>) { const { set } = useChangeSettings() const proInstancesQuery = useQuery({ queryKey: QueryKeys.PRO_INSTANCES, queryFn: async () => { const proInstances = (await client.pro.listProInstances({ authenticate: true })).unwrap() if (proInstances !== undefined && proInstances.length > 0) { set("experimental_devPodPro", true) } return proInstances }, refetchInterval: REFETCH_INTERVAL_MS, }) const value = useMemo( () => [ proInstancesQuery.data, { status: proInstancesQuery.status, error: proInstancesQuery.error }, ], [proInstancesQuery.data, proInstancesQuery.status, proInstancesQuery.error] ) return {children} } ================================================ FILE: desktop/src/contexts/DevPodContext/proInstances/context.ts ================================================ import { createContext } from "react" import { TProInstances, TQueryResult } from "../../../types" export type TProInstancesContext = TQueryResult export const ProInstancesContext = createContext([ [], ] as unknown as TProInstancesContext) ================================================ FILE: desktop/src/contexts/DevPodContext/proInstances/index.ts ================================================ export { useProInstances } from "./useProInstances" export { useProInstanceManager } from "./useProInstanceManager" export { ProInstancesProvider } from "./ProInstancesProvider" export { ProInstancesContext } from "./context" export type { TProInstancesContext } from "./context" ================================================ FILE: desktop/src/contexts/DevPodContext/proInstances/useProInstanceManager.ts ================================================ import { DaemonClient } from "@/client/pro/client" import { Err, Failed } from "@/lib" import { useMutation, useQueryClient } from "@tanstack/react-query" import { useMemo } from "react" import { client } from "../../../client" import { QueryKeys } from "../../../queryKeys" import { TProInstanceLoginConfig, TProInstanceManager, TProvider, TWithProID } from "../../../types" const FALLBACK_PROVIDER_NAME = "devpod-pro" export function useProInstanceManager(): TProInstanceManager { const queryClient = useQueryClient() const loginMutation = useMutation({ mutationFn: async ({ host, accessKey, streamListener }) => { ;(await client.pro.login(host, accessKey, streamListener)).unwrap() // if we don't have a provider name, check for the pro instance and then use it's provider name const proInstances = (await client.pro.listProInstances()).unwrap() const maybeNewInstance = proInstances?.find((instance) => instance.host === host) let maybeProviderName = maybeNewInstance?.provider if (maybeNewInstance) { const proClient = client.getProClient(maybeNewInstance) if (proClient instanceof DaemonClient) { await proClient.restartDaemon() } } try { const providers = (await client.providers.listAll()).unwrap() if (providers === undefined || Object.keys(providers).length === 0) { throw new Error("No providers found") } if (!maybeProviderName) { maybeProviderName = FALLBACK_PROVIDER_NAME } const maybeProvider = providers[maybeProviderName] if (!maybeProvider) { throw new Error(`Provider ${maybeProviderName} not found`) } return maybeProvider } catch (e) { ;(await client.pro.removeProInstance(host)).unwrap() throw e } }, onSuccess: () => { queryClient.invalidateQueries(QueryKeys.PRO_INSTANCES) queryClient.invalidateQueries(QueryKeys.PROVIDERS) }, }) const disconnectMutation = useMutation, TWithProID>({ mutationFn: async ({ id }) => (await client.pro.removeProInstance(id)).unwrap(), onSuccess: () => { queryClient.invalidateQueries(QueryKeys.PRO_INSTANCES) queryClient.invalidateQueries(QueryKeys.PROVIDERS) }, }) return useMemo( () => ({ login: { run: loginMutation.mutate, status: loginMutation.status, error: loginMutation.error, reset: loginMutation.reset, provider: loginMutation.data, }, disconnect: { run: disconnectMutation.mutate, status: disconnectMutation.status, error: disconnectMutation.error, target: disconnectMutation.variables, }, }), [disconnectMutation, loginMutation] ) } ================================================ FILE: desktop/src/contexts/DevPodContext/proInstances/useProInstances.tsx ================================================ import { useContext } from "react" import { TProInstanceManager } from "../../../types" import { useProInstanceManager } from "./useProInstanceManager" import { ProInstancesContext, TProInstancesContext } from "./context" export function useProInstances(): [TProInstancesContext, TProInstanceManager] { const proInstances = useContext(ProInstancesContext) const manager = useProInstanceManager() return [proInstances, manager] } ================================================ FILE: desktop/src/contexts/DevPodContext/useProvider.ts ================================================ import { TProvider, TProviderID, TProviderManager } from "../../types" import { useProviders } from "./useProviders" export function useProvider( providerID: TProviderID | undefined | null ): [TProvider | undefined, TProviderManager] { const [[providers], manager] = useProviders() return [providerID ? providers?.[providerID] : undefined, manager] } ================================================ FILE: desktop/src/contexts/DevPodContext/useProviderManager.ts ================================================ import { useMutation, useQueryClient } from "@tanstack/react-query" import { useMemo } from "react" import { client } from "../../client" import { exists } from "../../lib" import { QueryKeys } from "../../queryKeys" import { TProviderManager, TProviders, TWithProviderID } from "../../types" export function useProviderManager(): TProviderManager { const queryClient = useQueryClient() const removeMutation = useMutation({ mutationFn: async ({ providerID }: TWithProviderID) => (await client.providers.remove(providerID)).unwrap(), onMutate({ providerID }) { // Optimistically updates `delete` mutation queryClient.cancelQueries(QueryKeys.PROVIDERS) const oldProviderSnapshot = queryClient.getQueryData(QueryKeys.PROVIDERS)?.[ providerID ] queryClient.setQueryData(QueryKeys.PROVIDERS, (current) => { const shallowCopy = { ...current } delete shallowCopy[providerID] return shallowCopy }) return { oldProviderSnapshot } }, onError(_, { providerID }, ctx) { const maybeOldProvider = ctx?.oldProviderSnapshot if (exists(maybeOldProvider)) { queryClient.setQueryData(QueryKeys.PROVIDERS, (current) => ({ ...current, [providerID]: maybeOldProvider, })) } }, onSuccess(_, { providerID }) { queryClient.invalidateQueries(QueryKeys.provider(providerID)) }, }) return useMemo( () => ({ remove: { run: removeMutation.mutate, status: removeMutation.status, error: removeMutation.error, target: removeMutation.variables, }, }), [removeMutation] ) } ================================================ FILE: desktop/src/contexts/DevPodContext/useProviders.ts ================================================ import { useContext } from "react" import { TProviderManager } from "../../types" import { DevPodContext, TDevpodContext } from "./DevPodProvider" import { useProviderManager } from "./useProviderManager" export function useProviders(): [TDevpodContext["providers"] | [undefined], TProviderManager] { const providers = useContext(DevPodContext)?.providers ?? [undefined] const manager = useProviderManager() return [providers, manager] } ================================================ FILE: desktop/src/contexts/DevPodContext/workspaceStore/WorkspaceStoreProvider.tsx ================================================ import { ReactNode, useMemo } from "react" import { WorkspaceStoreContext } from "./context" import { IWorkspaceStore } from "./workspaceStore" type TWorkspaceStoreProps = Readonly<{ store: TStore children?: ReactNode }> export function WorkspaceStoreProvider>({ children, store, }: TWorkspaceStoreProps) { const value = useMemo(() => ({ store }), [store]) return {children} } ================================================ FILE: desktop/src/contexts/DevPodContext/workspaceStore/context.ts ================================================ import { createContext } from "react" import { IWorkspaceStore } from "./workspaceStore" export type TWorkspaceStoreContext = Readonly<{ store: IWorkspaceStore }> export const WorkspaceStoreContext = createContext(null!) ================================================ FILE: desktop/src/contexts/DevPodContext/workspaceStore/index.ts ================================================ export { WorkspaceStoreProvider } from "./WorkspaceStoreProvider" export { useWorkspaceStore } from "./useWorkspaceStore" export { WorkspaceStore, ProWorkspaceStore } from "./workspaceStore" export type { IWorkspaceStore } from "./workspaceStore" ================================================ FILE: desktop/src/contexts/DevPodContext/workspaceStore/types.ts ================================================ ================================================ FILE: desktop/src/contexts/DevPodContext/workspaceStore/useWorkspaceStore.ts ================================================ import { useContext } from "react" import { WorkspaceStoreContext } from "./context" import { IWorkspaceStore } from "./workspaceStore" export function useWorkspaceStore>() { const { store } = useContext(WorkspaceStoreContext) return { store: store as T } } ================================================ FILE: desktop/src/contexts/DevPodContext/workspaceStore/workspaceStore.ts ================================================ import { debug, EventManager, SingleEventManager } from "../../../lib" import { TProID, TUnsubscribeFn, TWorkspace, TWorkspaceID, TWorkspaceWithoutStatus, } from "../../../types" import { ProWorkspaceInstance } from "../Pro" import { Action, TActionFn, TActionName, TActionObj } from "../action" import { ActionHistory } from "../action/actionHistory" // This is a workaround for how typescript resolves circular dependencies, usually the import should be from "./action" import { replaceEqualDeep } from "../helpers" type TLastActions = Readonly<{ active: readonly TActionObj[]; history: readonly TActionObj[] }> type TStartActionArgs = Readonly<{ actionName: TActionName workspaceKey: TInstanceID actionFn: TActionFn }> export interface IWorkspaceStore { // workspaces get(id: TKey): TW | undefined getAll(): readonly TW[] setWorkspace(id: TKey, newWorkspace: TW): void setWorkspaces(newWorkspaces: readonly TW[]): void removeWorkspace(workspaceKey: TKey): void subscribe(listener: VoidFunction): TUnsubscribeFn setStatus(workspaceKey: TKey, status: string | null | undefined): void // workspace actions getAllActions(): TLastActions getCurrentAction(workspaceKey: TKey): TActionObj | undefined getWorkspaceActions(workspaceKey: TKey): TActionObj[] startAction(args: TStartActionArgs): Action["id"] } class InternalWorkspaceStore { private readonly eventManager = new SingleEventManager() private actionsHistory: ActionHistory private workspaces = new Map() private lastWorkspaces: readonly TWorkspace[] = [] private lastActions: TLastActions = { active: [], history: [] } constructor(key?: string) { this.actionsHistory = new ActionHistory(key) this.lastActions = this.actionsHistory.getAll() } public subscribe(listener: VoidFunction): TUnsubscribeFn { const handler = EventManager.toHandler(listener) return this.eventManager.subscribe(handler) } public get(key: TKey): TWorkspace | undefined { return this.workspaces.get(key) } public getAll(): readonly TWorkspace[] { return this.lastWorkspaces } public getWorkspaceActions(workspaceKey: TKey): TActionObj[] { return [ ...this.lastActions.active.filter((action) => action.targetID === workspaceKey), ...this.lastActions.history.filter((action) => action.targetID === workspaceKey).reverse(), ] } public getCurrentAction(workspaceKey: TKey): TActionObj | undefined { return this.lastActions.active.find((action) => action.targetID === workspaceKey) } public getAllActions(): TLastActions { return this.lastActions } public removeWorkspace(workspaceKey: TKey): void { this.workspaces.delete(workspaceKey) this.workspacesDidChange() } public startAction({ actionName, workspaceKey, actionFn }: TStartActionArgs): Action["id"] { // By default, actions cancel previous actios. // If you need to wait for an action to finish, you can use `getCurrentAction` and wait until it is undefined const maybeCurrentAction = this.actionsHistory.getActive(workspaceKey) if (maybeCurrentAction !== undefined) { maybeCurrentAction.cancel() this.actionsHistory.archive(maybeCurrentAction) } const action = new Action(actionName, workspaceKey, actionFn) this.actionsHistory.addActive(workspaceKey, action) // Setup listener for when the action is done action.once(() => { // We need to give the UI a chance to listen to the settled state, so we need to inform it about the change once // before and once after archiving the action this.actionDidChange() this.actionsHistory.archive(action) // Notify react on next tick of event loop to check the actions once more. // This ensures we have a chance to move from the `pending` to one of the `settled` states with the UI noticing. setTimeout(() => { this.actionDidChange() }, 0) }) action.run() this.actionDidChange() return action.id } public setWorkspaces(newWorkspaces: Map) { this.workspaces = newWorkspaces this.workspacesDidChange() } public setWorkspace(id: TKey, newWorkspace: TWorkspace) { this.workspaces.set(id, newWorkspace) this.workspacesDidChange() } private actionDidChange() { this.lastActions = this.actionsHistory.getAll() this.eventManager.publish() debug("actions", this.lastActions) } private workspacesDidChange() { this.lastWorkspaces = Array.from(this.workspaces.values()) this.eventManager.publish() debug("workspaces", this.lastWorkspaces) } } export class WorkspaceStore implements IWorkspaceStore { private store = new InternalWorkspaceStore() public get(id: TWorkspaceID): TWorkspace | undefined { return this.store.get(id) } public getAll(): readonly TWorkspace[] { return this.store.getAll() } public setWorkspace(id: TWorkspaceID, newWorkspace: TWorkspace): void { return this.store.setWorkspace(id, newWorkspace) } public setWorkspaces(newWorkspaces: readonly TWorkspaceWithoutStatus[]): void { const prevWorkspaces = this.store.getAll().map((workspace) => { // we need to remove `status` before comparing the workspaces because the new ones will not have it. // eslint-disable-next-line @typescript-eslint/no-unused-vars const { status: _, ...w } = workspace return w }) const workspaces = replaceEqualDeep(prevWorkspaces, newWorkspaces) if (Object.is(workspaces, prevWorkspaces)) { return } const newWorkspacesMap = new Map( workspaces.map((workspace) => { // patch existing status if we have one for this workspace - new ones will be sent without it const maybeExistingWorkspace = this.store.get(workspace.id) return [workspace.id, { ...workspace, status: maybeExistingWorkspace?.status }] }) ) this.store.setWorkspaces(newWorkspacesMap) } public removeWorkspace(workspaceID: TWorkspaceID): void { return this.store.removeWorkspace(workspaceID) } public subscribe(listener: VoidFunction): TUnsubscribeFn { return this.store.subscribe(listener) } public setStatus(workspaceID: TWorkspaceID, status: string | null | undefined): void { const maybeWorkspace = this.store.get(workspaceID) if (maybeWorkspace === undefined) { return } const prevStatus = maybeWorkspace.status if (status === prevStatus) { return } this.store.setWorkspace(workspaceID, { ...maybeWorkspace, status: status as TWorkspace["status"], }) } public getAllActions(): TLastActions { return this.store.getAllActions() } public getCurrentAction(workspaceID: TWorkspaceID): TActionObj | undefined { return this.store.getCurrentAction(workspaceID) } public getWorkspaceActions(workspaceID: TWorkspaceID): TActionObj[] { return this.store.getWorkspaceActions(workspaceID) } public startAction(args: TStartActionArgs): Action["id"] { return this.store.startAction(args) } } type TInstanceID = string export class ProWorkspaceStore implements IWorkspaceStore { private store: InternalWorkspaceStore constructor(id: TProID) { this.store = new InternalWorkspaceStore(id) } public get(key: TInstanceID): ProWorkspaceInstance | undefined { return this.store.get(key) } public getAll(): readonly ProWorkspaceInstance[] { return this.store.getAll() } public setWorkspace(key: TInstanceID, newWorkspace: ProWorkspaceInstance): void { return this.store.setWorkspace(key, newWorkspace) } public setWorkspaces(newInstances: readonly ProWorkspaceInstance[]): void { const prevInstances = this.store.getAll() const instances = replaceEqualDeep(prevInstances, newInstances) if (Object.is(instances, prevInstances)) { return } const newWorkspacesMap = new Map(instances.map((instance) => [instance.id, instance])) this.store.setWorkspaces(newWorkspacesMap) } public removeWorkspace(workspaceKey: TInstanceID): void { return this.store.removeWorkspace(workspaceKey) } public subscribe(listener: VoidFunction): TUnsubscribeFn { return this.store.subscribe(listener) } // eslint-disable-next-line @typescript-eslint/no-unused-vars public setStatus(_workspaceKey: TInstanceID, _status: string): void { // noop return } public getAllActions(): TLastActions { return this.store.getAllActions() } public getCurrentAction(workspaceKey: TInstanceID): TActionObj | undefined { return this.store.getCurrentAction(workspaceKey) } public getWorkspaceActions(workspaceKey: TInstanceID): TActionObj[] { return this.store.getWorkspaceActions(workspaceKey) } public startAction(args: TStartActionArgs): Action["id"] { return this.store.startAction(args) } } ================================================ FILE: desktop/src/contexts/DevPodContext/workspaces/index.ts ================================================ export { usePollWorkspaces } from "./usePollWorkspaces" export { useWorkspace, useWorkspaceActions, startWorkspaceAction, stopWorkspaceAction, removeWorkspaceAction, } from "./useWorkspace" export { useWorkspaces } from "./useWorkspaces" export { useAllWorkspaceActions } from "./useAllWorkspaceActions" ================================================ FILE: desktop/src/contexts/DevPodContext/workspaces/useAllWorkspaceActions.ts ================================================ import { useCallback, useSyncExternalStore } from "react" import { TActionObj } from "../action" import { useWorkspaceStore } from "../workspaceStore" export function useAllWorkspaceActions() { const { store } = useWorkspaceStore() const actions = useSyncExternalStore( useCallback((listener) => store.subscribe(listener), [store]), () => store.getAllActions() ) return { active: actions.active, history: actions.history.slice().sort(sortByCreationDesc) } } function sortByCreationDesc(a: TActionObj, b: TActionObj) { return b.createdAt - a.createdAt } ================================================ FILE: desktop/src/contexts/DevPodContext/workspaces/usePollWorkspaces.ts ================================================ import { useCallback, useEffect } from "react" import { client } from "../../../client" import { TWorkspaceID } from "../../../types" import { REFETCH_INTERVAL_MS } from "../constants" import { WorkspaceStore, useWorkspaceStore } from "../workspaceStore" export function usePollWorkspaces() { const { store } = useWorkspaceStore() const listWorkspaces = useCallback(async () => { const result = await client.workspaces.listAll() if (result.err) { return } store.setWorkspaces(result.val) }, [store]) const updateStatus = useCallback( async (ongoingRequests: Record) => { for (const workspace of store.getAll()) { // Don't kick off a request if we already have one in flight or if we're executing an action on this workspace const currentAction = store.getCurrentAction(workspace.id) if (ongoingRequests[workspace.id] !== undefined || currentAction != undefined) { continue } ongoingRequests[workspace.id] = true try { const result = await client.workspaces.getStatus(workspace.id) if (result.err) { continue } // We don't care about the order here, we just want to update the status // whenever we get a result back store.setStatus(workspace.id, result.val) } finally { delete ongoingRequests[workspace.id] } } }, [store] ) useEffect(() => { const workspacesIntervalID = setInterval(listWorkspaces, REFETCH_INTERVAL_MS) const ongoingRequests: Record = {} const statusIntervalID = setInterval(async () => { await updateStatus(ongoingRequests) }, REFETCH_INTERVAL_MS) const initialTimeoutID = setTimeout(async () => { await listWorkspaces() await updateStatus(ongoingRequests) }, 0) return () => { clearInterval(workspacesIntervalID) clearInterval(statusIntervalID) clearTimeout(initialTimeoutID) } }, [listWorkspaces, updateStatus]) } ================================================ FILE: desktop/src/contexts/DevPodContext/workspaces/useWorkspace.ts ================================================ import { useCallback, useId, useMemo, useRef, useSyncExternalStore } from "react" import { TStreamEventListenerFn, client } from "../../../client" import { exists } from "../../../lib" import { TIdentifiable, TStreamID, TWorkspaceID, TWorkspaceStartConfig } from "../../../types" import { TActionID, TActionObj, useConnectAction, useReplayAction } from "../action" import { IWorkspaceStore, ProWorkspaceStore, useWorkspaceStore } from "../workspaceStore" export type TWorkspaceResult = Readonly<{ data: T | undefined isLoading: boolean current: | (TActionObj & Readonly<{ connect: (listener: TStreamEventListenerFn) => void }>) | undefined history: Readonly<{ // all: readonly TActionObj[] replay: (actionID: TActionID, listener: TStreamEventListenerFn) => void }> start: (config: TWorkspaceStartConfig, onStream?: TStreamEventListenerFn) => TActionID | undefined create: ( config: Omit & Pick & Readonly<{ workspaceKey?: string }>, onStream?: TStreamEventListenerFn ) => TActionID stop: (onStream?: TStreamEventListenerFn) => TActionID | undefined remove: (force: boolean, onStream?: TStreamEventListenerFn) => TActionID | undefined rebuild: (onStream?: TStreamEventListenerFn) => TActionID | undefined reset: (onStream?: TStreamEventListenerFn) => TActionID | undefined checkStatus: (onStream?: TStreamEventListenerFn) => TActionID | undefined }> export function useWorkspaceActions( workspaceID: TWorkspaceID | undefined ): TActionObj[] | undefined { const { store } = useWorkspaceStore() const dataCache = useRef() const data = useSyncExternalStore( useCallback((listener) => store.subscribe(listener), [store]), () => { if (workspaceID === undefined) { return undefined } // It's okay to use sort directly here because the store always returns a new array const workspaceActions = store.getWorkspaceActions(workspaceID).sort((a, b) => { if (a.finishedAt && b.finishedAt) { return b.finishedAt - a.finishedAt } return b.createdAt - a.createdAt }) if (!dataCache.current || dataCache.current.length !== workspaceActions.length) { dataCache.current = workspaceActions return dataCache.current } // compare actions const diff = dataCache.current.filter( (action) => !workspaceActions.find((workspaceAction) => action.id === workspaceAction.id) ) if (diff.length > 0) { dataCache.current = workspaceActions return dataCache.current } return dataCache.current } ) return data } export function useWorkspace( workspaceKey: string | undefined ): TWorkspaceResult { const { store } = useWorkspaceStore>() const viewID = useId() const data = useSyncExternalStore( useCallback((listener) => store.subscribe(listener), [store]), () => (workspaceKey !== undefined ? store.get(workspaceKey) : undefined) ) const workspaceID = useMemo(() => { if (!data) { return undefined } return data.id }, [data]) const create = useCallback["create"]>( (config, onStream) => { return store.startAction({ actionName: "start", workspaceKey: config.workspaceKey ?? config.id, actionFn: async (ctx) => { const result = await client.workspaces.start(config, onStream, { id: config.id, actionID: ctx.id, streamID: viewID, }) if (result.err) { return result } store.setStatus(config.id, result.val) return result }, }) }, [store, viewID] ) const start = useCallback["start"]>( (config, onStream) => { if (workspaceID === undefined) { return } return startWorkspaceAction({ workspaceID, config, onStream, streamID: viewID, store }) }, [store, viewID, workspaceID] ) const checkStatus = useCallback["checkStatus"]>( (onStream) => { if (workspaceID === undefined) { return } return store.startAction({ actionName: "checkStatus", workspaceKey: workspaceID, actionFn: async (ctx) => { const result = await client.workspaces.checkStatus(onStream, { id: workspaceID, actionID: ctx.id, streamID: viewID, }) if (result.err) { return result } store.setStatus(workspaceID, result.val) return result }, }) }, [store, viewID, workspaceID] ) const stop = useCallback["stop"]>( (onStream) => { if (workspaceID === undefined) { return } return stopWorkspaceAction({ workspaceID, onStream, streamID: viewID, store }) }, [store, viewID, workspaceID] ) const rebuild = useCallback["rebuild"]>( (onStream) => { if (workspaceID === undefined) { return } return store.startAction({ actionName: "rebuild", workspaceKey: workspaceID, actionFn: async (ctx) => { const result = await client.workspaces.rebuild(onStream, { id: workspaceID, actionID: ctx.id, streamID: viewID, }) if (result.err) { return result } store.setStatus(workspaceID, result.val) return result }, }) }, [store, viewID, workspaceID] ) const reset = useCallback["reset"]>( (onStream) => { if (workspaceID === undefined) { return } return store.startAction({ actionName: "reset", workspaceKey: workspaceID, actionFn: async (ctx) => { const result = await client.workspaces.reset(onStream, { id: workspaceID, actionID: ctx.id, streamID: viewID, }) if (result.err) { return result } store.setStatus(workspaceID, result.val) return result }, }) }, [store, viewID, workspaceID] ) const remove = useCallback["remove"]>( (force, onStream) => { if (workspaceID === undefined) { return } return removeWorkspaceAction({ force, workspaceID, onStream, streamID: viewID, store }) }, [store, viewID, workspaceID] ) const currentAction = useSyncExternalStore( useCallback((listener) => store.subscribe(listener), [store]), () => (workspaceID !== undefined ? store.getCurrentAction(workspaceID) : undefined) ) const isLoading = useMemo(() => exists(currentAction), [currentAction]) const connect = useConnectAction(currentAction, viewID) const current = useMemo["current"]>(() => { if (currentAction === undefined) { return undefined } return { ...currentAction, connect, } }, [currentAction, connect]) const replay = useReplayAction() const history = useMemo["history"]>(() => { return { replay } }, [replay]) return useMemo( () => ({ data, isLoading, current, history, create, start, stop, rebuild, reset, remove, checkStatus, }), [data, isLoading, current, history, create, start, stop, rebuild, reset, remove, checkStatus] ) } type TStartWorkspaceActionArgs = Readonly<{ config: TWorkspaceStartConfig onStream?: TStreamEventListenerFn workspaceID: TWorkspaceID streamID: TStreamID store: IWorkspaceStore }> export function startWorkspaceAction({ workspaceID, streamID, config, onStream, store, }: TStartWorkspaceActionArgs): TActionObj["id"] { return store.startAction({ actionName: "start", workspaceKey: workspaceID, actionFn: async (ctx) => { const result = await client.workspaces.start(config, onStream, { id: workspaceID, actionID: ctx.id, streamID, }) if (result.err) { return result } store.setStatus(workspaceID, result.val) return result }, }) } type TStopWorkspaceActionArgs = Readonly<{ onStream?: TStreamEventListenerFn workspaceID: TWorkspaceID streamID: TStreamID store: IWorkspaceStore }> export function stopWorkspaceAction({ workspaceID, onStream, streamID, store, }: TStopWorkspaceActionArgs): TActionObj["id"] { return store.startAction({ actionName: "stop", workspaceKey: workspaceID, actionFn: async (ctx) => { const result = await client.workspaces.stop(onStream, { id: workspaceID, actionID: ctx.id, streamID, }) if (result.err) { return result } store.setStatus(workspaceID, result.val) return result }, }) } type TRemoveWorkspaceActionArgs = Readonly<{ onStream?: TStreamEventListenerFn workspaceID: TWorkspaceID streamID: TStreamID force: boolean store: IWorkspaceStore }> export function removeWorkspaceAction({ workspaceID, onStream, streamID, force, store, }: TRemoveWorkspaceActionArgs): TActionObj["id"] { return store.startAction({ actionName: "remove", workspaceKey: workspaceID, actionFn: async (ctx) => { const result = await client.workspaces.remove(force, onStream, { id: workspaceID, actionID: ctx.id, streamID, }) if (result.err) { return result } // Pro Desktop app will get updates through watcher, no need to remove from local store if (store instanceof ProWorkspaceStore) { return result } store.removeWorkspace(workspaceID) return result }, }) } ================================================ FILE: desktop/src/contexts/DevPodContext/workspaces/useWorkspaces.ts ================================================ import { useCallback, useSyncExternalStore } from "react" import { IWorkspaceStore, useWorkspaceStore } from "../workspaceStore" export function useWorkspaces(): readonly TW[] { const { store } = useWorkspaceStore>() const workspaces = useSyncExternalStore( useCallback((listener) => store.subscribe(listener), [store]), () => store.getAll() ) return workspaces } ================================================ FILE: desktop/src/contexts/SettingsContext/SettingsContext.tsx ================================================ import { ReactNode, useCallback, useEffect, useMemo, useState } from "react" import { client } from "@/client" import { getKeys, LocalStorageToFileMigrationBackend, Store } from "@/lib" import { TUnsubscribeFn } from "@/types" import { TSetting, TSettings, TSettingsContext, SettingsContext } from "./useSettings" const initialSettings: TSettings = { sidebarPosition: "left", debugFlag: false, partyParrot: false, fixedIDE: false, zoom: "md", transparency: false, autoUpdate: true, additionalCliFlags: "", additionalEnvVars: "", dotfilesUrl: "", sshKeyPath: "", httpProxyUrl: "", httpsProxyUrl: "", noProxy: "", experimental_colorMode: "light", experimental_multiDevcontainer: false, experimental_fleet: true, experimental_jupyterNotebooks: true, experimental_vscodeInsiders: true, experimental_cursor: true, experimental_positron: true, experimental_zed: true, experimental_codium: true, experimental_rstudio: true, experimental_windsurf: true, experimental_devPodPro: false, } function getSettingKeys(): readonly TSetting[] { return getKeys(initialSettings) } // WARN: needs to match the filename on the rust side const SETTING_STORE_KEY = "settings" const settingsStore = new Store>( new LocalStorageToFileMigrationBackend(SETTING_STORE_KEY) ) export function SettingsProvider({ children }: Readonly<{ children?: ReactNode }>) { const [settings, setSettings] = useState(initialSettings) useEffect(() => { ;(async () => { const initialOptions = await Promise.all( getSettingKeys().map((option) => settingsStore .get(option) .then((value) => [option, value ?? initialSettings[option]] as const) .catch(() => [option, false] as const) ) ) setSettings( initialOptions.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), initialSettings) ) })() }, []) useEffect(() => { const subscriptions: TUnsubscribeFn[] = [] for (const setting of getSettingKeys()) { subscriptions.push( settingsStore.subscribe(setting, (newValue) => setSettings((current) => ({ ...current, [setting]: newValue })) ) ) } return () => { for (const unsubscribe of subscriptions) { unsubscribe() } } }, []) useEffect(() => { client.setSetting("debugFlag", settings.debugFlag) }, [settings.debugFlag]) useEffect(() => { client.setSetting("additionalCliFlags", settings.additionalCliFlags) }, [settings.additionalCliFlags]) useEffect(() => { client.setSetting("dotfilesUrl", settings.dotfilesUrl) }, [settings.dotfilesUrl]) useEffect(() => { client.setSetting("additionalEnvVars", settings.additionalEnvVars) }, [settings.additionalEnvVars]) useEffect(() => { client.setSetting("httpProxyUrl", settings.httpProxyUrl) client.setSetting("httpsProxyUrl", settings.httpsProxyUrl) client.setSetting("noProxy", settings.noProxy) }, [settings.httpProxyUrl, settings.httpsProxyUrl, settings.noProxy]) const set = useCallback((key, value) => { settingsStore.set(key, value) }, []) const value = useMemo(() => ({ settings, set }), [set, settings]) return {children} } ================================================ FILE: desktop/src/contexts/SettingsContext/index.ts ================================================ export { useSettings, useChangeSettings } from "./useSettings" export { SettingsProvider } from "./SettingsContext" export type { TSettingsContext, TSettings } from "./useSettings" ================================================ FILE: desktop/src/contexts/SettingsContext/useSettings.ts ================================================ import { createContext, useContext } from "react" import { Settings } from "@/gen" export type TSettings = Settings export type TSetting = keyof TSettings export type TSettingsContext = Readonly<{ settings: TSettings set(setting: TKey, value: TSettings[TKey]): void }> export const SettingsContext = createContext(null!) export function useSettings() { return useContext(SettingsContext).settings } export function useChangeSettings() { return useContext(SettingsContext) } ================================================ FILE: desktop/src/contexts/ToolbarContext/ToolbarContext.tsx ================================================ import { ReactNode, useCallback, useMemo, useState } from "react" import { TToolbarAction, TToolbarContext, ToolbarContext } from "./useToolbar" export function ToolbarProvider({ children }: Readonly<{ children: ReactNode }>) { const [title, setTitle] = useState(null) const [actions, setActions] = useState([]) const addAction = useCallback((id, node) => { setActions((actions) => { const newActions = actions.slice() const index = newActions.findIndex((a) => a.id === id) if (index !== -1) { newActions.splice(index, 1, { id, node: node }) } else { newActions.push({ id, node: node }) } return newActions }) return () => { setActions((actions) => actions.filter((a) => a.id !== id)) } }, []) const value = useMemo( () => ({ title, setTitle, actions: actions.map((a) => a.node), addAction }), [title, actions, addAction] ) return {children} } ================================================ FILE: desktop/src/contexts/ToolbarContext/index.ts ================================================ export * from "./ToolbarContext" export { useToolbar } from "./useToolbar" ================================================ FILE: desktop/src/contexts/ToolbarContext/useToolbar.ts ================================================ import { ReactNode, createContext, useContext } from "react" export type TToolbarContext = Readonly<{ title: ReactNode setTitle: (title: ReactNode) => void actions: readonly ReactNode[] addAction: (id: string, action: ReactNode) => void }> export type TToolbarAction = Readonly<{ id: string node: ReactNode }> export const ToolbarContext = createContext(null!) export function useToolbar() { return useContext(ToolbarContext) } ================================================ FILE: desktop/src/contexts/index.ts ================================================ export * from "./DevPodContext" export * from "./ToolbarContext" export { SettingsProvider, useSettings, useChangeSettings } from "./SettingsContext" export type { TSettings } from "./SettingsContext" ================================================ FILE: desktop/src/gen/Asset.ts ================================================ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export interface Asset { url: string browser_download_url: string id: bigint node_id: string name: string label: string | null state: string content_type: string size: bigint download_count: bigint created_at: string updated_at: string } ================================================ FILE: desktop/src/gen/Author.ts ================================================ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export interface Author { login: string id: bigint node_id: string avatar_url: String gravatar_id: string url: string html_url: string followers_url: string following_url: string gists_url: string starred_url: string subscriptions_url: string organizations_url: string repos_url: string events_url: string received_events_url: string type: string site_admin: boolean } ================================================ FILE: desktop/src/gen/ColorMode.ts ================================================ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type ColorMode = "dark" | "light" ================================================ FILE: desktop/src/gen/DaemonState.ts ================================================ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type DaemonState = "stopped" | "pending" | "running" ================================================ FILE: desktop/src/gen/DaemonStatus.ts ================================================ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { DaemonState } from "./DaemonState" export interface DaemonStatus { state: DaemonState online: boolean loginRequired: boolean } ================================================ FILE: desktop/src/gen/Release.ts ================================================ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { Asset } from "./Asset" import type { Author } from "./Author" export interface Release { url: String html_url: string assets_url: string upload_url: string tarball_url: string | null zipball_url: string | null id: bigint node_id: string tag_name: string target_commitish: string name: string | null body: string | null draft: boolean prerelease: boolean created_at: string | null published_at: string | null author: Author assets: Array } ================================================ FILE: desktop/src/gen/Settings.ts ================================================ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ColorMode } from "./ColorMode" import type { SidebarPosition } from "./SidebarPosition" import type { Zoom } from "./Zoom" export interface Settings { sidebarPosition: SidebarPosition debugFlag: boolean partyParrot: boolean fixedIDE: boolean zoom: Zoom transparency: boolean autoUpdate: boolean additionalCliFlags: string additionalEnvVars: string dotfilesUrl: string sshKeyPath: string httpProxyUrl: string httpsProxyUrl: string noProxy: string experimental_multiDevcontainer: boolean experimental_fleet: boolean experimental_jupyterNotebooks: boolean experimental_vscodeInsiders: boolean experimental_cursor: boolean experimental_codium: boolean experimental_zed: boolean experimental_positron: boolean experimental_rstudio: boolean experimental_windsurf: boolean experimental_devPodPro: boolean experimental_colorMode: ColorMode } ================================================ FILE: desktop/src/gen/SidebarPosition.ts ================================================ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type SidebarPosition = "left" | "right" ================================================ FILE: desktop/src/gen/Zoom.ts ================================================ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type Zoom = "sm" | "md" | "lg" | "xl" ================================================ FILE: desktop/src/gen/index.ts ================================================ export * from "./Asset" export * from "./Author" export * from "./ColorMode" export * from "./DaemonState" export * from "./DaemonStatus" export * from "./Release" export * from "./Settings" export * from "./SidebarPosition" export * from "./Zoom" export * from "./index" ================================================ FILE: desktop/src/icons/ArrowCycle.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const ArrowCycle = createIcon({ displayName: "ArrowCycle", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/ArrowDown.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const ArrowDown = createIcon({ displayName: "ArrowDown", viewBox: "0 0 16 16", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/ArrowLeft.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const ArrowLeft = createIcon({ displayName: "ArrowLeft", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/ArrowPath.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const ArrowPath = createIcon({ displayName: "ArrowPath", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/ArrowUp.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const ArrowUp = createIcon({ displayName: "ArrowUp", viewBox: "0 0 16 16", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Bell.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Bell = createIcon({ displayName: "Bell", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/BellDuotone.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const BellDuotone = createIcon({ displayName: "BellDuotone", viewBox: "0 0 16 16", defaultProps, path: [ , , ], }) ================================================ FILE: desktop/src/icons/Briefcase.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Briefcase = createIcon({ displayName: "Briefcase", viewBox: "0 0 20 20", defaultProps, path: [ , , ], }) ================================================ FILE: desktop/src/icons/CPU.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const CPU = createIcon({ displayName: "CPU", viewBox: "0 0 24 24", defaultProps, path: [ , , , , , , , , , , ], }) ================================================ FILE: desktop/src/icons/CheckCircle.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const CheckCircle = createIcon({ displayName: "CheckCircle", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/CircleDuotone.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const CircleDuotone = createIcon({ displayName: "CircleDuotone", viewBox: "0 0 16 16", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/CircleWithArrow.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const CircleWithArrow = createIcon({ displayName: "CircleWithArrow", viewBox: "0 0 16 17", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Clock.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Clock = createIcon({ displayName: "Clock", viewBox: "0 0 16 16", defaultProps, path: [ , , ], }) ================================================ FILE: desktop/src/icons/Close.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Close = createIcon({ displayName: "Close", viewBox: "0 0 16 16", defaultProps, path: [ , , ], }) ================================================ FILE: desktop/src/icons/Code.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Code = createIcon({ displayName: "Code", viewBox: "0 0 16 16", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Cog.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Cog = createIcon({ displayName: "Cog", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/CogDuotone.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const CogDuotone = createIcon({ displayName: "CogDuotone", viewBox: "0 0 16 16", defaultProps, path: [ , , , , ], }) ================================================ FILE: desktop/src/icons/CogOutlined.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const CogOutlined = createIcon({ displayName: "CogOutlined", viewBox: "0 0 16 17", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/CommandLine.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const CommandLine = createIcon({ displayName: "CommandLine", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Connect.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Connect = createIcon({ displayName: "Connect", viewBox: "0 0 16 16", defaultProps, path: [ , , ], }) ================================================ FILE: desktop/src/icons/Dashboard.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Dashboard = createIcon({ displayName: "Dashboard", viewBox: "0 0 24 24", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Database.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Database = createIcon({ displayName: "Database", viewBox: "0 0 24 24", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/DevPodProBadge.tsx ================================================ import { Icon, IconProps, useColorMode } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const DevPodProBadge = (props: IconProps) => { const { colorMode } = useColorMode() return ( ) } ================================================ FILE: desktop/src/icons/Devpod.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const DevPodIcon = createIcon({ displayName: "DevPodIcon", viewBox: "0 0 184 152", defaultProps, path: [ , , ], }) ================================================ FILE: desktop/src/icons/DevpodWordmark.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const DevpodWordmark = createIcon({ displayName: "DevpodWordmark", viewBox: "0 0 648 153", defaultProps, path: [ , , ], }) ================================================ FILE: desktop/src/icons/Ellipsis.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Ellipsis = createIcon({ displayName: "Ellipsis", viewBox: "64 64 896 896", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/ExclamationCircle.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const ExclamationCircle = createIcon({ displayName: "ExclamationCircle", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/ExclamationTriangle.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const ExclamationTriangle = createIcon({ displayName: "ExclamationTriangle", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/File.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const File = createIcon({ displayName: "File", viewBox: "0 0 24 24", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Folder.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Folder = createIcon({ displayName: "Folder", viewBox: "0 0 24 24", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Form.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Form = createIcon({ displayName: "Form", viewBox: "0 0 24 24", defaultProps, path: [ , , ], }) ================================================ FILE: desktop/src/icons/Git.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Git = createIcon({ displayName: "Git", viewBox: "0 0 92 92", defaultProps, path: [ , , ], }) ================================================ FILE: desktop/src/icons/GitBranch.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const GitBranch = createIcon({ displayName: "GitBranch", viewBox: "0 0 12 14", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/GitCommit.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const GitCommit = createIcon({ displayName: "GitCommit", viewBox: "0 0 16 16", defaultProps, path: [ , , ], }) ================================================ FILE: desktop/src/icons/GitPR.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const GitPR = createIcon({ displayName: "GitPR", viewBox: "0 0 24 24", defaultProps, path: [ , , ], }) ================================================ FILE: desktop/src/icons/GitSubPath.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const GitSubPath = createIcon({ displayName: "GitSubPath", viewBox: "0 0 24 24", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Globe.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Globe = createIcon({ displayName: "Globe", viewBox: "0 0 16 16", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Gold.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Gold = createIcon({ displayName: "Gold", viewBox: "0 0 16 16", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/History.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const History = createIcon({ displayName: "History", viewBox: "0 0 24 24", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Image.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Image = createIcon({ displayName: "Image", viewBox: "0 0 24 24", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Laptop.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Laptop = createIcon({ displayName: "Laptop", viewBox: "0 0 16 16", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/LockDuotone.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const LockDuotone = createIcon({ displayName: "LockDuotone", viewBox: "0 0 24 24", defaultProps, path: [ , , , ], }) ================================================ FILE: desktop/src/icons/Loft.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Loft = createIcon({ displayName: "Loft", viewBox: "0 0 270.6 137.3", defaultProps, path: [ , , , , ], }) ================================================ FILE: desktop/src/icons/LoftDevpodPro.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const LoftDevPodPro = createIcon({ displayName: "LoftDevPodPro", viewBox: "0 0 684 350", defaultProps, path: [ , , , ], }) ================================================ FILE: desktop/src/icons/MatchCase.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "@/icons/defaultProps" export const MatchCase = createIcon({ displayName: "MatchCase", viewBox: "0 0 16 16", defaultProps, path: ( <> ), }) ================================================ FILE: desktop/src/icons/Memory.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Memory = createIcon({ displayName: "Memory", viewBox: "0 0 24 24", defaultProps, path: [ , , , , , , , , , ], }) ================================================ FILE: desktop/src/icons/NotFound.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const NotFound = createIcon({ displayName: "NotFound", viewBox: "0 0 12 14", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Parameters.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Parameters = createIcon({ displayName: "Parameters", viewBox: "0 0 16 16", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Pause.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Pause = createIcon({ displayName: "Pause", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Play.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Play = createIcon({ displayName: "Play", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Plus.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Plus = createIcon({ displayName: "Plus", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Preset.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Preset = createIcon({ displayName: "Preset", viewBox: "0 0 16 16", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/ProfileDuotone.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const ProfileDuotone = createIcon({ displayName: "ProfileDuotone", viewBox: "0 0 24 24", defaultProps, path: [ , , , ], }) ================================================ FILE: desktop/src/icons/ProviderPlaceholder.tsx ================================================ import { Icon, IconProps, useColorModeValue, useToken } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export function ProviderPlaceholder(iconProps: IconProps) { const circleFillColorToken = useColorModeValue("gray.100", "gray.700") const circleFillColor = useToken("colors", circleFillColorToken) return ( ) } ================================================ FILE: desktop/src/icons/Search.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "@/icons/defaultProps" export const Search = createIcon({ displayName: "Search", viewBox: "0 0 16 16", defaultProps, path: ( <> ), }) ================================================ FILE: desktop/src/icons/Sleep.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Sleep = createIcon({ displayName: "Sleep", viewBox: "0 0 16 16", defaultProps, path: [ , , , ], }) ================================================ FILE: desktop/src/icons/Stack3D.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Stack3D = createIcon({ displayName: "Stack3D", viewBox: "0 0 20 20", defaultProps, path: [ , , , ], }) ================================================ FILE: desktop/src/icons/Status.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Status = createIcon({ displayName: "Status", viewBox: "0 0 16 17", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Stop.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Stop = createIcon({ displayName: "Stop", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Template.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Template = createIcon({ displayName: "Template", viewBox: "0 0 24 25", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/Trash.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const Trash = createIcon({ displayName: "Trash", viewBox: "0 0 20 20", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/User.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const User = createIcon({ displayName: "User", viewBox: "0 0 24 24", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/WholeWord.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "@/icons/defaultProps" export const WholeWord = createIcon({ displayName: "WholeWord", viewBox: "0 0 16 16", defaultProps, path: ( <> ), }) ================================================ FILE: desktop/src/icons/WorkspaceStatus.tsx ================================================ import { createIcon } from "@chakra-ui/react" import { defaultProps } from "./defaultProps" export const WorkspaceStatus = createIcon({ displayName: "WorkspaceStatus", viewBox: "0 0 12 12", defaultProps, path: ( ), }) ================================================ FILE: desktop/src/icons/defaultProps.tsx ================================================ import { IconProps } from "@chakra-ui/react" export const defaultProps: IconProps = { boxSize: 6, fill: "currentColor" } ================================================ FILE: desktop/src/icons/index.ts ================================================ export * from "./Devpod" export { ArrowLeft } from "./ArrowLeft" export { ArrowPath } from "./ArrowPath" export { ArrowCycle } from "./ArrowCycle" export { Bell } from "./Bell" export { Briefcase } from "./Briefcase" export { CheckCircle } from "./CheckCircle" export { CircleDuotone } from "./CircleDuotone" export { Cog } from "./Cog" export { CogOutlined } from "./CogOutlined" export { CommandLine } from "./CommandLine" export { DevpodWordmark } from "./DevpodWordmark" export { DevPodProBadge } from "./DevPodProBadge" export { Ellipsis } from "./Ellipsis" export { ExclamationCircle } from "./ExclamationCircle" export { ExclamationTriangle } from "./ExclamationTriangle" export { Pause } from "./Pause" export { Stop } from "./Stop" export { Loft } from "./Loft" export { LoftDevPodPro } from "./LoftDevpodPro" export { Play } from "./Play" export { Plus } from "./Plus" export { ProviderPlaceholder } from "./ProviderPlaceholder" export { Stack3D } from "./Stack3D" export { WorkspaceStatus } from "./WorkspaceStatus" export { Trash } from "./Trash" export { Template } from "./Template" export { GitBranch } from "./GitBranch" export { GitCommit } from "./GitCommit" export { GitPR } from "./GitPR" export { GitSubPath } from "./GitSubPath" export { Status } from "./Status" export { Git } from "./Git" export { Image } from "./Image" export { Folder } from "./Folder" export { Globe } from "./Globe" export { Clock } from "./Clock" export { CogDuotone } from "./CogDuotone" export { BellDuotone } from "./BellDuotone" export { CircleWithArrow } from "./CircleWithArrow" export { Preset } from "./Preset" export { Code } from "./Code" export { Parameters } from "./Parameters" export { Laptop } from "./Laptop" export { Sleep } from "./Sleep" export { NotFound } from "./NotFound" export { Connect } from "./Connect" export { Close } from "./Close" export { MatchCase } from "./MatchCase" export { WholeWord } from "./WholeWord" export { ArrowUp } from "./ArrowUp" export { ArrowDown } from "./ArrowDown" export { Search } from "./Search" export { User } from "./User" export { ProfileDuotone } from "./ProfileDuotone" export { LockDuotone } from "./LockDuotone" export { Form } from "./Form" export { File } from "./File" export { Dashboard } from "./Dashboard" export { History } from "./History" export { Memory } from "./Memory" export { Database } from "./Database" export { CPU } from "./CPU" ================================================ FILE: desktop/src/images/index.ts ================================================ export { default as NoWorkspaceImageSvg } from "./empty_workspace.svg" export { default as CppSvg } from "./cpp.svg" export { default as CustomSvg } from "./custom.svg" export { default as CommunitySvg } from "./community.svg" export { default as SSHSvg } from "./ssh.svg" export { default as DotnetcoreSvg } from "./dotnet.svg" export { default as GoSvg } from "./go.svg" export { default as GoSvgDark } from "./go_dark.svg" export { default as JavaSvg } from "./java.svg" export { default as NodejsSvg } from "./nodejs.svg" export { default as NoneSvg } from "./none.svg" export { default as NoneSvgDark } from "./none_dark.svg" export { default as PhpSvg } from "./php.svg" export { default as PhpSvgDark } from "./php_dark.svg" export { default as PythonSvg } from "./python.svg" export { default as RustSvg } from "./rust.svg" export { default as RustSvgDark } from "./rust_dark.svg" export { default as DockerSvg } from "./docker.svg" export { default as KubernetesSvg } from "./kubernetes.svg" export { default as GCloudSvg } from "./gcp.svg" export { default as AWSSvg } from "./aws.svg" export { default as AWSWhiteSvg } from "./aws_white.svg" export { default as DigitalOceanSvg } from "./digitalocean.svg" export { default as AzureSvg } from "./azure.svg" export { default as CivoSvg } from "./civo.svg" export { default as TerraformSvg } from "./terraform.svg" export { default as VSCodeBrowser } from "./vscodebrowser.svg" export { default as VSCodeSvg } from "./vscode.svg" export { default as VSCodeInsidersSvg } from "./vscode_insiders.svg" export { default as ProviderPlaceholderSvg } from "./provider_placeholder.svg" export { default as RubySvg } from "./ruby.svg" export { default as IntelliJSvg } from "./intellij.svg" export { default as GolandSvg } from "./goland.svg" export { default as PycharmSvg } from "./pycharm.svg" export { default as WebstormSvg } from "./webstorm.svg" export { default as CLionSvg } from "./clion.svg" export { default as RiderSvg } from "./rider.svg" export { default as RubyMineSvg } from "./rubymine.svg" export { default as RustRoverSvg } from "./rustrover.svg" export { default as PHPStormSvg } from "./phpstorm.svg" export { default as DataSpellSvg } from "./dataspell.svg" export { default as FleetSvg } from "./fleet.svg" export { default as CursorSvg } from "./cursor.svg" export { default as JupyterNotebookSvg } from "./jupyter.svg" export { default as JupyterNotebookDarkSvg } from "./jupyter_dark.svg" export { default as PositronSvg } from "./positron.svg" export { default as CodiumSvg } from "./codium.svg" export { default as ZedSvg } from "./zed.svg" export { default as ZedDarkSvg } from "./zed_dark.svg" export { default as RStudioSvg } from "./rstudio.svg" export { default as WindsurfSvg } from "./windsurf.svg" ================================================ FILE: desktop/src/lib/debugSettings.ts ================================================ import { useEffect, useState } from "react" import { TUnsubscribeFn } from "../types" import { LocalStorageBackend, Store } from "./store" const DEBUG_STORE_KEY = "debug" const DEBUG_OPTIONS = ["commands", "actions", "workspaces"] as const type TDebugOption = (typeof DEBUG_OPTIONS)[number] type TDebug = Readonly<{ isEnabled?: boolean toggle?(option: TDebugOption): Promise get?(option: TDebugOption): Promise print?(): void }> type TDebugStore = Record type TInternalDebug = Readonly<{ subscribe(option: TDebugOption, listener: (newValue: boolean) => void): TUnsubscribeFn }> function init(): TDebug & TInternalDebug { const store = new Store(new LocalStorageBackend(DEBUG_STORE_KEY)) return { isEnabled: true, async toggle(option) { const current = (await store.get(option)) ?? false const newOptionValue = !current await store.set(option, newOptionValue) }, async get(option) { return (await store.get(option)) ?? false }, subscribe(option, listener) { return store.subscribe(option, listener) }, print() { console.log(store) }, } } const initialDebugOptions: TDebugStore = { commands: false, workspaces: false, actions: false } type TUseDebug = Readonly<{ options: Record }> & Pick function useInternalDebug(): TUseDebug { const [options, setOptions] = useState(initialDebugOptions) useEffect(() => { ;(async () => { const initialOptions = await Promise.all( DEBUG_OPTIONS.map((option) => Debug.get!(option) .then((value) => [option, value] as const) .catch(() => [option, false] as const) ) ) setOptions( initialOptions.reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {} as TDebugStore) ) })() }, []) useEffect(() => { const subscriptions: TUnsubscribeFn[] = [] for (const option of DEBUG_OPTIONS) { subscriptions.push( (Debug as TInternalDebug).subscribe(option, (newValue) => setOptions((currentOptions) => ({ ...currentOptions, [option]: newValue })) ) ) } return () => { for (const unsubscribe of subscriptions) { unsubscribe() } } }, []) return { options, isEnabled: true } } export function debug(option: TDebugOption, ...args: Parameters<(typeof console)["info"]>): void { Debug.get?.(option).then((isEnabled) => { if (isEnabled) { console.info(...args) } }) } // Only available during development export const Debug: TDebug = import.meta.env.DEV ? init() : { isEnabled: false } // Only available during development export const useDebug: typeof useInternalDebug = import.meta.env.DEV ? useInternalDebug : () => ({ options: initialDebugOptions, isEnabled: false }) ================================================ FILE: desktop/src/lib/eventManager.ts ================================================ import { TComparable, TIdentifiable, TUnsubscribeFn } from "../types" import { exists, isEmpty } from "./helpers" import { v4 as uuidv4 } from "uuid" type TEventHandler = THandler< (event: TEvents[TEventName]) => void > export type THandler = Readonly<{ notify: TFn }> & TIdentifiable & TComparable type TBaseEvents = Record type TEventManager = Readonly<{ subscribe: ( eventName: TEventName, handler: TEventHandler ) => TUnsubscribeFn isSubscribed: ( eventName: TEventName, handler: TEventHandler ) => boolean unsubscribe: ( eventName: TEventName, handler: TEventHandler ) => void publish: ( eventName: TEventName, event: TEvents[TEventName] ) => boolean clear: (eventName: TEventName) => void }> export class EventManager implements TEventManager { private handlers = new Map[]>() public static toHandler( listenerFn: TFn, id: string = uuidv4() ): THandler { return { id, eq(other) { return this.id === other.id }, notify: listenerFn, } } public subscribe( eventName: TEventName, handler: TEventHandler ): VoidFunction { const maybeHandlers = this.handlers.get(eventName) if (!exists(maybeHandlers)) { this.handlers.set(eventName, [handler as TEventHandler]) } else { this.handlers.set(eventName, [...maybeHandlers, handler as TEventHandler]) } return () => this.unsubscribe(eventName, handler) } public isSubscribed( eventName: TEventName, handler: TEventHandler ): boolean { const maybeHandlers = this.handlers.get(eventName) if (!exists(maybeHandlers)) { return false } return exists(maybeHandlers.find((l) => l.eq(handler))) } public unsubscribe( eventName: TEventName, handler: TEventHandler ): void { const maybeEventHandlers = this.handlers.get(eventName) if (!exists(maybeEventHandlers)) { return } this.handlers.set( eventName, maybeEventHandlers.filter((h) => !h.eq(handler)) ) if (isEmpty(maybeEventHandlers)) { this.handlers.delete(eventName) } } public publish( eventName: TEventName, event: TEvents[TEventName] ): boolean { const maybeHandlers = this.handlers.get(eventName) if (!exists(maybeHandlers)) { return false } for (const handler of maybeHandlers) { handler.notify(event) } return true } public clear(eventName: TEventName): void { this.handlers.delete(eventName) } } export class SingleEventManager { private manager = new EventManager<{ event: T }>() subscribe(handler: TEventHandler<{ event: T }, "event">): VoidFunction { return this.manager.subscribe("event", handler) } isSubscribed(handler: TEventHandler<{ event: T }, "event">): boolean { return this.manager.isSubscribed("event", handler) } unsubscribe(handler: TEventHandler<{ event: T }, "event">): void { return this.manager.unsubscribe("event", handler) } publish(event: T): boolean { return this.manager.publish("event", event) } clear(): void { return this.manager.clear("event") } } ================================================ FILE: desktop/src/lib/helpers.ts ================================================ import { TIDE, TLogOutput, TProInstance, TProvider } from "../types" import { ChildProcess } from "@tauri-apps/plugin-shell" import { Err, Failed, Return } from "./result" import { TActionObj } from "../contexts" import { WORKSPACE_SOURCE_BRANCH_DELIMITER, WORKSPACE_SOURCE_COMMIT_DELIMITER } from "@/constants" import { TWorkspace, TIDEs } from "@/types" export function exists( arg: T ): arg is Exclude { return arg !== undefined && arg !== null } export function isError(error: unknown): error is Error { return error instanceof Error } export function noop(): void {} export async function noopAsync(): Promise {} // WARN: All keys and values of `map` need to be serializable by `JSON.stringify` for this to work! // See the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description) // if you're unsure export function serializeMap>(map: T): string { return JSON.stringify(Array.from(map.entries())) } export function deserializeMap>(serializedMap: string): T { return new Map(JSON.parse(serializedMap)) as T } export function isEmpty(arg: T): boolean { return arg.length <= 0 } export function safeJSONParse(arg: string): T | null { try { return JSON.parse(arg) as T } catch { return null } } export function getErrorFromChildProcess(result: ChildProcess): Err { const stdout = parseOutput(result.stdout) const stderr = parseOutput(result.stderr) const sorted = [...stdout, ...stderr].sort((a, b) => { if (a.time === b.time) { return 0 } const aTime = new Date(a.time).getTime() || 0 const bTime = new Date(b.time).getTime() || 0 if (aTime < bTime) { return -1 } return 1 }) const message: string[] = sorted.reduce((acc, log) => { const line = log.message?.trim() if (!line) { return acc } acc.push(line) return acc }, [] as string[]) return Return.Failed(message.join("\n")) } export function parseOutput(arg: string): TLogOutput[] { const retOutput: TLogOutput[] = arg.split("\n").reduce((acc, line) => { const trimmed = line.trim() if (!trimmed) { return acc } const logLine = safeJSONParse(line) as TLogOutput | undefined if (!logLine?.message) { return acc } acc.push(logLine) return acc }, [] as TLogOutput[]) return retOutput } export function getKeys(arg: T): readonly (keyof T)[] { return Object.keys(arg) as unknown as readonly (keyof T)[] } export function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)) } export function getActionDisplayName(action: TActionObj): string { if (action.name === "checkStatus") { return `check status ${action.targetID}` } return `${action.name} ${action.targetID}` } export function getIDEDisplayName(ide: TIDE) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition return ide.displayName ?? ide.name ?? "Unknown" } export function randomString(length: number): string { return [...Array(length)].map(() => (~~(Math.random() * 36)).toString(36)).join("") } export function remToPx(rem: string): number { return parseFloat(rem) * parseFloat(getComputedStyle(document.documentElement).fontSize) } export function getIDEName(ide: TWorkspace["ide"], ides: TIDEs | undefined) { const maybeIDE = ides?.find((i) => i.name === ide?.name) return maybeIDE?.displayName ?? ide?.name ?? maybeIDE?.name ?? "Unknown" } export function getWorkspaceSourceName({ gitRepository, gitBranch, gitCommit, localFolder, image, }: NonNullable): string { if (exists(gitRepository) && exists(gitCommit)) { return `${gitRepository}${WORKSPACE_SOURCE_COMMIT_DELIMITER}${gitCommit}` } if (exists(gitRepository) && exists(gitBranch)) { return `${gitRepository}${WORKSPACE_SOURCE_BRANCH_DELIMITER}${gitBranch}` } if (exists(gitRepository)) { return gitRepository } if (exists(image)) { return image } if (exists(localFolder)) { return localFolder } return "" } export function deepCopy(obj: T): T | undefined { if (obj === undefined) { return undefined } return JSON.parse(JSON.stringify(obj)) } export function canHealthCheck(providerConfig: TProvider["config"]): boolean { return !!providerConfig?.exec?.proxy?.["health"] || !!providerConfig?.exec?.daemon } export function hasCapability( proInstance: TProInstance | undefined, capability: "daemon" | "update-provider" | "health-check" ): boolean { return proInstance?.capabilities?.includes(capability) ?? false } ================================================ FILE: desktop/src/lib/index.ts ================================================ export * from "./helpers" export { EventManager, SingleEventManager } from "./eventManager" export type { THandler } from "./eventManager" export { Debug, useDebug, debug } from "./debugSettings" export * from "./platform" export * from "./result" export { Store, LocalStorageBackend, FileStorageBackend, LocalStorageToFileMigrationBackend, } from "./store" export { useArch, usePlatform, useSystemTheme } from "./systemInfo" export * from "./types" export * from "./releases" export { useFormErrors } from "./useFormErrors" export { useHover } from "./useHover" export { useVersion } from "./useVersion" export { useUpdate } from "./useUpdate" export { useDownloadLogs } from "./useDownloadLogs" export { useSelection } from "./useSelection" export * from "./useSortWorkspaces" export * from "./modals" export * from "./pro" ================================================ FILE: desktop/src/lib/modals/index.ts ================================================ export { useStopWorkspaceModal } from "./useStopWorkspaceModal" export { useDeleteWorkspaceModal } from "./useDeleteWorkspaceModal" export { useRebuildWorkspaceModal } from "./useRebuildWorkspaceModal" export { useResetWorkspaceModal } from "./useResetWorkspaceModal" export { useLoginProModal, useReLoginProModal } from "./useLoginProModal" ================================================ FILE: desktop/src/lib/modals/useDeleteWorkspaceModal.tsx ================================================ import { Box, Button, Checkbox, HStack, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useDisclosure, } from "@chakra-ui/react" import { useMemo, useState } from "react" export function useDeleteWorkspaceModal( workspaceName: string, onClick: (forceDelete: boolean, closeModal: VoidFunction) => void, force?: boolean ) { const { isOpen, onOpen, onClose } = useDisclosure() const [forceDelete, setForceDelete] = useState(false) const modal = useMemo( () => ( Delete Workspace Deleting the workspace will erase all state. Are you sure you want to delete{" "} {workspaceName}? {force == null && ( setForceDelete(e.target.checked)}> Force Delete the Workspace )} ), [force, forceDelete, isOpen, onClick, onClose, workspaceName] ) return { modal, open: onOpen } } ================================================ FILE: desktop/src/lib/modals/useLoginProModal.tsx ================================================ import { BottomActionBar, BottomActionBarError, Form, useStreamingTerminal } from "@/components" import { useProInstanceManager, useProInstances, useProviders } from "@/contexts" import { canHealthCheck, exists, useFormErrors } from "@/lib" import { Routes } from "@/routes" import { Box, Button, Container, Divider, FormControl, FormErrorMessage, FormHelperText, FormLabel, Heading, Input, InputGroup, InputLeftAddon, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Tooltip, VStack, useDisclosure, } from "@chakra-ui/react" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { SubmitHandler, useForm } from "react-hook-form" import { useNavigate } from "react-router" import { ConfigureProviderOptionsForm, useSetupProvider } from "@/views/Providers" import { To } from "react-router-dom" type TFormValues = { [FieldName.PRO_HOST]: string [FieldName.ACCESS_KEY]: string | undefined } const FieldName = { PRO_HOST: "proURL", ACCESS_KEY: "accessKey", } as const type TSetupProInitialData = { host: string accessKey?: string suggestedOptions: Record } export function useLoginProModal() { const [suggestedOptions, setSuggestedOptions] = useState< TSetupProInitialData["suggestedOptions"] >({}) const { terminal, connectStream, clear: clearTerminal } = useStreamingTerminal({ fontSize: "sm" }) const [[proInstances], { login, disconnect }] = useProInstances() const [[providers]] = useProviders() const { isOpen, onClose, onOpen } = useDisclosure() const { handleSubmit, formState, register, reset, setValue } = useForm({ mode: "onBlur", }) const containerRef = useRef(null) const onSubmit = useCallback>( (data) => { clearTerminal() login.run({ host: data[FieldName.PRO_HOST], accessKey: data[FieldName.ACCESS_KEY], streamListener: connectStream, }) }, [connectStream, login, clearTerminal] ) const handleOpenLogin = useCallback( (data?: TSetupProInitialData) => { onOpen() if (data === undefined || login.status === "loading") { return } setValue(FieldName.PRO_HOST, data.host) if (data.accessKey) { setValue(FieldName.ACCESS_KEY, data.accessKey) } setSuggestedOptions(data.suggestedOptions) handleSubmit(onSubmit)() }, [handleSubmit, login.status, onOpen, onSubmit, setValue] ) const { state, reset: resetSetupProvider, completeSetupProvider, completeConfigureProvider, removeDanglingProviders, } = useSetupProvider() const { proURLError } = useFormErrors(Object.values(FieldName), formState) useEffect(() => { if (login.status === "success") { const providerID = login.provider?.config?.name if (!exists(providerID)) { return } completeSetupProvider({ providerID, suggestedOptions }) } }, [completeSetupProvider, login.provider, login.status, suggestedOptions]) const resetModal = useCallback( (checkDanglingProInstance: boolean = false) => { reset() login.reset() if (checkDanglingProInstance) { const proInstanceID = proInstances?.find((pro) => pro.provider === state.providerID)?.host if (proInstanceID) { disconnect.run({ id: proInstanceID }) } } onClose() }, [disconnect, login, onClose, proInstances, reset, state.providerID] ) useEffect(() => { if (state.currentStep === "done") { resetSetupProvider() removeDanglingProviders() } }, [removeDanglingProviders, resetSetupProvider, state.currentStep]) const areInputsDisabled = useMemo( () => login.status === "success" || login.status === "loading", [login.status] ) const navigate = useNavigate() const completeFlow = useCallback(() => { completeConfigureProvider() resetModal() const proInstanceID = proInstances?.find((pro) => pro.provider === state.providerID)?.host if (!proInstanceID || !state.providerID) return const provider = providers?.[state.providerID] let route: To // We only redirect to the new experience if the provider supports it. // Support can be determined via canHealthCheck. if (provider && canHealthCheck(provider.config)) { route = Routes.toProInstance(proInstanceID) } else { route = Routes.toWorkspaceCreate({ workspaceID: null, ide: null, rawSource: null, providerID: state.providerID, }) } // workaround for layout shift after closing modal, no clue why setTimeout(() => { navigate(route) }, 0) }, [completeConfigureProvider, navigate, providers, proInstances, resetModal, state.providerID]) const modal = useMemo(() => { return ( resetModal(true)} isOpen={isOpen} closeOnEsc={login.status !== "loading"} closeOnOverlayClick={login.status !== "loading"} isCentered size="4xl" scrollBehavior="inside"> {login.status !== "loading" && } Connect to DevPod Pro
URL https:// { try { new URL(`https://${value.replace(/^https?:\/\//, "")}`) return true } catch { return "Please enter a valid URL" } }, unique: (value) => { const isHostTaken = proInstances?.some( (instance) => instance.host === value ) return isHostTaken ? `URL must be unique, an instance with the URL https://${value} already exists` : true }, }, })} /> {proURLError && proURLError.message ? ( {proURLError.message} ) : ( Enter a URL to the DevPod Pro instance you intend to connect to. If you're unsure about it, ask your company administrator or create a new Pro instance on your local machine. )} {login.status !== "idle" && state.currentStep === "select-provider" && ( {terminal} )} {state.currentStep !== "configure-provider" && ( )}
{state.currentStep === "configure-provider" && ( <> Configure your Pro provider )}
) }, [ resetModal, isOpen, login.status, login.error, handleSubmit, onSubmit, proURLError, areInputsDisabled, register, state, terminal, formState.isValid, formState.isSubmitting, completeFlow, proInstances, ]) return { modal, handleOpenLogin } } export function useReLoginProModal() { const { terminal, connectStream, clear: clearTerminal } = useStreamingTerminal({ fontSize: "sm" }) const { login } = useProInstanceManager() const { isOpen, onClose, onOpen } = useDisclosure() const containerRef = useRef(null) const handleOpenLogin = useCallback( (data: NonNullable>) => { onOpen() login.run({ host: data.host, streamListener: connectStream }) }, [connectStream, login, onOpen] ) const resetModal = useCallback(() => { clearTerminal() onClose() }, [clearTerminal, onClose]) const modal = useMemo(() => { return ( {login.status !== "loading" && } Login to DevPod Pro {login.status !== "idle" && ( {terminal} )} ) }, [resetModal, isOpen, login.status, terminal]) return { modal, handleOpenLogin } } ================================================ FILE: desktop/src/lib/modals/useRebuildWorkspaceModal.tsx ================================================ import { Button, HStack, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useDisclosure, } from "@chakra-ui/react" import { useMemo } from "react" export function useRebuildWorkspaceModal( workspaceName: string, onClick: (closeModal: VoidFunction) => void ) { const { isOpen, onOpen, onClose } = useDisclosure() const modal = useMemo( () => ( Rebuild Workspace Rebuilding the workspace will erase all state saved in the docker container overlay. This means you might need to reinstall or reconfigure certain applications. State in docker volumes is persisted. Are you sure you want to rebuild {workspaceName}? ), [isOpen, onClick, onClose, workspaceName] ) return { modal, open: onOpen } } ================================================ FILE: desktop/src/lib/modals/useResetWorkspaceModal.tsx ================================================ import { Button, HStack, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useDisclosure, } from "@chakra-ui/react" import { useMemo } from "react" export function useResetWorkspaceModal( workspaceName: string, onClick: (closeModal: VoidFunction) => void ) { const { isOpen, onOpen, onClose } = useDisclosure() const modal = useMemo( () => ( Reset Workspace Resetting the workspace will erase all state saved in the docker container overlay and DELETE ALL UNCOMMITTED CODE. This means you might need to reinstall or reconfigure certain applications. You can also just recreate the pod with rebuild without wiping all state. With reset, you will start with a fresh clone of the repository. Are you sure you want to reset {workspaceName}? ), [isOpen, onClick, onClose, workspaceName] ) return { modal, open: onOpen } } ================================================ FILE: desktop/src/lib/modals/useStopWorkspaceModal.tsx ================================================ import { Button, HStack, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, useDisclosure, } from "@chakra-ui/react" import { useMemo } from "react" export function useStopWorkspaceModal(onClick: (closeModal: VoidFunction) => void) { const { isOpen, onOpen, onClose } = useDisclosure() const modal = useMemo( () => ( Stop Workspace Stopping a workspace while it's not running may leave it in a corrupted state. Do you want to stop it regardless? ), [isOpen, onClick, onClose] ) return { modal, open: onOpen } } ================================================ FILE: desktop/src/lib/platform.ts ================================================ export const isLinux = import.meta.env.TAURI_ENV_PLATFORM === "linux" export const isMacOS = import.meta.env.TAURI_ENV_PLATFORM === "darwin" export const isWindows = import.meta.env.TAURI_ENV_PLATFORM === "windows" ================================================ FILE: desktop/src/lib/pro/constants.ts ================================================ export const WorkspaceInstanceSource = { prefixGit: "git:", prefixImage: "image:", prefixLocal: "local:", } export const Annotations = { WorkspaceSource: "loft.sh/workspace-source", SleepModeLastActivity: "sleepmode.loft.sh/last-activity", } export const Labels = { WorkspaceID: "loft.sh/workspace-id", WorkspaceUID: "loft.sh/workspace-uid", } ================================================ FILE: desktop/src/lib/pro/index.ts ================================================ export * from "./name" export * from "./constants" export * from "./source" export * from "./time" export * from "./parameters" export * from "./useConnectionStatus" ================================================ FILE: desktop/src/lib/pro/name.ts ================================================ export function getDisplayName( entity: | Readonly<{ metadata?: { name?: string } spec?: { displayName?: string } }> | undefined, fallback: string = "" ): string { if (entity?.spec?.displayName) { return entity.spec.displayName } if (entity?.metadata?.name) { return entity.metadata.name } return fallback } export async function safeMaxName(str: string, maxLength: number): Promise { if (str.length <= maxLength) { return str } try { const text = new TextEncoder().encode(str) const digest = await crypto.subtle.digest("SHA-256", text) const hash = new Uint8Array(digest).reduce((s, b) => s + b.toString(16).padStart(2, "0"), "") return `${str.substring(0, maxLength - 8)}-${hash.substring(0, 7)}` } catch (err) { console.error("Failed to hash string", str, err) return str.substring(0, maxLength) } } ================================================ FILE: desktop/src/lib/pro/parameters.tsx ================================================ import { ManagementV1DevPodWorkspaceInstance } from "@loft-enterprise/client/gen/models/managementV1DevPodWorkspaceInstance" import { ManagementV1DevPodWorkspaceTemplate } from "@loft-enterprise/client/gen/models/managementV1DevPodWorkspaceTemplate" import { StorageV1AppParameter } from "@loft-enterprise/client/gen/models/storageV1AppParameter" import { StorageV1DevPodWorkspaceTemplateVersion } from "@loft-enterprise/client/gen/models/storageV1DevPodWorkspaceTemplateVersion" import { compareVersions } from "compare-versions" import jsyaml from "js-yaml" export type TParameterWithValue = StorageV1AppParameter & { value?: string | number | boolean } export function getParametersWithValues( instance: ManagementV1DevPodWorkspaceInstance, template: ManagementV1DevPodWorkspaceTemplate ): readonly TParameterWithValue[] | undefined { let rawParameters: StorageV1AppParameter[] | undefined = template.spec?.parameters if (instance.spec?.templateRef?.version) { // find versioned parameters rawParameters = template.spec?.versions?.find( (version) => version.version === instance.spec?.templateRef?.version )?.parameters } else if (template.spec?.versions && template.spec.versions.length > 0) { // fall back to latest version rawParameters = template.spec.versions[0]?.parameters } if (!instance.spec?.parameters || !rawParameters) { return undefined } try { const out = jsyaml.load(instance.spec.parameters) as Record return rawParameters.map((param) => { const path = param.variable if (path) { return { ...param, value: out[path] } } return param }) } catch { return undefined } } export function getParameters( template: ManagementV1DevPodWorkspaceTemplate | undefined, selectedVersion: string | undefined ): readonly StorageV1AppParameter[] | undefined { if (!template?.spec) { return undefined } if (selectedVersion) { return template.spec.versions?.find((version) => version.version === selectedVersion) ?.parameters } if (template.spec.versions && template.spec.versions.length > 0) { const latestVersion = findLatestVersion(template.spec.versions) if (latestVersion) { return template.spec.versions.find((version) => version.version === latestVersion.version) ?.parameters } } return template.spec.parameters } function findLatestVersion( versions: readonly StorageV1DevPodWorkspaceTemplateVersion[] ): StorageV1DevPodWorkspaceTemplateVersion | undefined { return versions.slice().sort(sortByVersionDesc)[0] } export function sortByVersionDesc( a: StorageV1DevPodWorkspaceTemplateVersion, b: StorageV1DevPodWorkspaceTemplateVersion ): number { return compareVersions(stripVersionPrefix(b.version ?? ""), stripVersionPrefix(a.version ?? "")) } function stripVersionPrefix(version: string): string { if (version.startsWith("v")) { return version.substring(1, version.length) } return version } ================================================ FILE: desktop/src/lib/pro/source.ts ================================================ import { WorkspaceInstanceSource } from "./constants" import { TWorkspaceSourceType } from "@/types" export class Source { readonly type: TWorkspaceSourceType readonly value: string constructor(type?: TWorkspaceSourceType, value?: string) { this.type = type ?? "git" this.value = value ?? "" } static fromRaw(rawSource?: string): Source { if (rawSource?.startsWith(WorkspaceInstanceSource.prefixGit)) { return new Source("git", rawSource.replace(WorkspaceInstanceSource.prefixGit, "")) } if (rawSource?.startsWith(WorkspaceInstanceSource.prefixImage)) { return new Source("image", rawSource.replace(WorkspaceInstanceSource.prefixImage, "")) } if (rawSource?.startsWith(WorkspaceInstanceSource.prefixLocal)) { return new Source("local", rawSource.replace(WorkspaceInstanceSource.prefixLocal, "")) } return new Source() } public stringify(): string { const value = this.value.trim() switch (this.type) { case "git": return `${WorkspaceInstanceSource.prefixGit}${value}` case "image": return `${WorkspaceInstanceSource.prefixImage}${value}` case "local": return `${WorkspaceInstanceSource.prefixLocal}${value}` } } } ================================================ FILE: desktop/src/lib/pro/time.ts ================================================ import { ProWorkspaceInstance } from "@/contexts" import { Annotations } from "./constants" export function getLastActivity(instance: ProWorkspaceInstance): Date | undefined { const maybeTimestamp = instance.metadata?.annotations?.[Annotations.SleepModeLastActivity] if (!maybeTimestamp) { return undefined } const timestamp = Number.parseInt(maybeTimestamp) if (Number.isNaN(timestamp)) { return undefined } return new Date(timestamp * 1_000) } ================================================ FILE: desktop/src/lib/pro/useConnectionStatus.tsx ================================================ import { useProContext } from "@/contexts" import { QueryKeys } from "@/queryKeys" import { TPlatformHealthCheck } from "@/types" import { useQuery } from "@tanstack/react-query" export type TConnectionStatus = Partial & { isLoading: boolean } export function useConnectionStatus(): TConnectionStatus { const { host, client } = useProContext() const { data: connection, isLoading } = useQuery({ queryKey: QueryKeys.connectionStatus(host), queryFn: async () => { try { const platformRes = await client.checkHealth() if (platformRes.ok) { return platformRes.val } return { healthy: false } } catch { return { healthy: false } } }, refetchInterval: 5_000, }) return { ...connection, isLoading } } ================================================ FILE: desktop/src/lib/randomWords.ts ================================================ const words = [ "ability", "able", "aboard", "about", "above", "accept", "accident", "according", "account", "accurate", "acres", "across", "act", "action", "active", "activity", "actual", "actually", "add", "addition", "additional", "adjective", "adult", "adventure", "advice", "affect", "afraid", "after", "afternoon", "again", "against", "age", "ago", "agree", "ahead", "aid", "air", "airplane", "alike", "alive", "all", "allow", "almost", "alone", "along", "aloud", "alphabet", "already", "also", "although", "am", "among", "amount", "ancient", "angle", "angry", "animal", "announced", "another", "answer", "ants", "any", "anybody", "anyone", "anything", "anyway", "anywhere", "apart", "apartment", "appearance", "apple", "applied", "appropriate", "are", "area", "arm", "army", "around", "arrange", "arrangement", "arrive", "arrow", "art", "article", "as", "aside", "ask", "asleep", "at", "ate", "atmosphere", "atom", "atomic", "attached", "attack", "attempt", "attention", "audience", "author", "automobile", "available", "average", "avoid", "aware", "away", "baby", "back", "bad", "badly", "bag", "balance", "ball", "balloon", "band", "bank", "bar", "bare", "bark", "barn", "base", "baseball", "basic", "basis", "basket", "bat", "battle", "be", "bean", "bear", "beat", "beautiful", "beauty", "became", "because", "become", "becoming", "bee", "been", "before", "began", "beginning", "begun", "behavior", "behind", "being", "believed", "bell", "belong", "below", "belt", "bend", "beneath", "bent", "beside", "best", "bet", "better", "between", "beyond", "bicycle", "bigger", "biggest", "bill", "birds", "birth", "birthday", "bit", "bite", "black", "blank", "blanket", "blew", "blind", "block", "blood", "blow", "blue", "board", "boat", "body", "bone", "book", "border", "born", "both", "bottle", "bottom", "bound", "bow", "bowl", "box", "boy", "brain", "branch", "brass", "brave", "bread", "break", "breakfast", "breath", "breathe", "breathing", "breeze", "brick", "bridge", "brief", "bright", "bring", "broad", "broke", "broken", "brother", "brought", "brown", "brush", "buffalo", "build", "building", "built", "buried", "burn", "burst", "bus", "bush", "business", "busy", "but", "butter", "buy", "by", "cabin", "cage", "cake", "call", "calm", "came", "camera", "camp", "can", "canal", "cannot", "cap", "capital", "captain", "captured", "car", "carbon", "card", "care", "careful", "carefully", "carried", "carry", "case", "cast", "castle", "cat", "catch", "cattle", "caught", "cause", "cave", "cell", "cent", "center", "central", "century", "certain", "certainly", "chain", "chair", "chamber", "chance", "change", "changing", "chapter", "character", "characteristic", "charge", "chart", "check", "cheese", "chemical", "chest", "chicken", "chief", "child", "children", "choice", "choose", "chose", "chosen", "church", "circle", "circus", "citizen", "city", "class", "classroom", "claws", "clay", "clean", "clear", "clearly", "climate", "climb", "clock", "close", "closely", "closer", "cloth", "clothes", "clothing", "cloud", "club", "coach", "coal", "coast", "coat", "coffee", "cold", "collect", "college", "colony", "color", "column", "combination", "combine", "come", "comfortable", "coming", "command", "common", "community", "company", "compare", "compass", "complete", "completely", "complex", "composed", "composition", "compound", "concerned", "condition", "congress", "connected", "consider", "consist", "consonant", "constantly", "construction", "contain", "continent", "continued", "contrast", "control", "conversation", "cook", "cookies", "cool", "copper", "copy", "corn", "corner", "correct", "correctly", "cost", "cotton", "could", "count", "country", "couple", "courage", "course", "court", "cover", "cow", "cowboy", "crack", "cream", "create", "creature", "crew", "crop", "cross", "crowd", "cry", "cup", "curious", "current", "curve", "customs", "cut", "cutting", "daily", "damage", "dance", "danger", "dangerous", "dark", "darkness", "date", "daughter", "dawn", "day", "dead", "deal", "dear", "death", "decide", "declared", "deep", "deeply", "deer", "definition", "degree", "depend", "depth", "describe", "desert", "design", "desk", "detail", "determine", "develop", "development", "diagram", "diameter", "did", "die", "differ", "difference", "different", "difficult", "difficulty", "dig", "dinner", "direct", "direction", "directly", "dirt", "dirty", "disappear", "discover", "discovery", "discuss", "discussion", "disease", "dish", "distance", "distant", "divide", "division", "do", "doctor", "does", "dog", "doing", "doll", "dollar", "done", "donkey", "door", "dot", "double", "doubt", "down", "dozen", "draw", "drawn", "dream", "dress", "drew", "dried", "drink", "drive", "driven", "driver", "driving", "drop", "dropped", "drove", "dry", "duck", "due", "dug", "dull", "during", "dust", "duty", "each", "eager", "ear", "earlier", "early", "earn", "earth", "easier", "easily", "east", "easy", "eat", "eaten", "edge", "education", "effect", "effort", "egg", "eight", "either", "electric", "electricity", "element", "elephant", "eleven", "else", "empty", "end", "enemy", "energy", "engine", "engineer", "enjoy", "enough", "enter", "entire", "entirely", "environment", "equal", "equally", "equator", "equipment", "escape", "especially", "essential", "establish", "even", "evening", "event", "eventually", "ever", "every", "everybody", "everyone", "everything", "everywhere", "evidence", "exact", "exactly", "examine", "example", "excellent", "except", "exchange", "excited", "excitement", "exciting", "exclaimed", "exercise", "exist", "expect", "experience", "experiment", "explain", "explanation", "explore", "express", "expression", "extra", "eye", "face", "facing", "fact", "factor", "factory", "failed", "fair", "fairly", "fall", "fallen", "familiar", "family", "famous", "far", "farm", "farmer", "farther", "fast", "fastened", "faster", "fat", "father", "favorite", "fear", "feathers", "feature", "fed", "feed", "feel", "feet", "fell", "fellow", "felt", "fence", "few", "fewer", "field", "fierce", "fifteen", "fifth", "fifty", "fight", "fighting", "figure", "fill", "film", "final", "finally", "find", "fine", "finest", "finger", "finish", "fire", "fireplace", "firm", "first", "fish", "five", "fix", "flag", "flame", "flat", "flew", "flies", "flight", "floating", "floor", "flow", "flower", "fly", "fog", "folks", "follow", "food", "foot", "football", "for", "force", "foreign", "forest", "forget", "forgot", "forgotten", "form", "former", "fort", "forth", "forty", "forward", "fought", "found", "four", "fourth", "fox", "frame", "free", "freedom", "frequently", "fresh", "friend", "friendly", "frighten", "frog", "from", "front", "frozen", "fruit", "fuel", "full", "fully", "fun", "function", "funny", "fur", "furniture", "further", "future", "gain", "game", "garage", "garden", "gas", "gasoline", "gate", "gather", "gave", "general", "generally", "gentle", "gently", "get", "getting", "giant", "gift", "girl", "give", "given", "giving", "glad", "glass", "globe", "go", "goes", "gold", "golden", "gone", "good", "goose", "got", "government", "grabbed", "grade", "gradually", "grain", "grandfather", "grandmother", "graph", "grass", "gravity", "gray", "great", "greater", "greatest", "greatly", "green", "grew", "ground", "group", "grow", "grown", "growth", "guard", "guess", "guide", "gulf", "gun", "habit", "had", "hair", "half", "halfway", "hall", "hand", "handle", "handsome", "hang", "happen", "happened", "happily", "happy", "harbor", "hard", "harder", "hardly", "has", "hat", "have", "having", "hay", "he", "headed", "heading", "health", "heard", "hearing", "heart", "heat", "heavy", "height", "held", "hello", "help", "helpful", "her", "herd", "here", "herself", "hidden", "hide", "high", "higher", "highest", "highway", "hill", "him", "himself", "his", "history", "hit", "hold", "hole", "hollow", "home", "honor", "hope", "horn", "horse", "hospital", "hot", "hour", "house", "how", "however", "huge", "human", "hundred", "hung", "hungry", "hunt", "hunter", "hurried", "hurry", "hurt", "husband", "ice", "idea", "identity", "if", "ill", "image", "imagine", "immediately", "importance", "important", "impossible", "improve", "in", "inch", "include", "including", "income", "increase", "indeed", "independent", "indicate", "individual", "industrial", "industry", "influence", "information", "inside", "instance", "instant", "instead", "instrument", "interest", "interior", "into", "introduced", "invented", "involved", "iron", "is", "island", "it", "its", "itself", "jack", "jar", "jet", "job", "join", "joined", "journey", "joy", "judge", "jump", "jungle", "just", "keep", "kept", "key", "kids", "kill", "kind", "kitchen", "knew", "knife", "know", "knowledge", "known", "label", "labor", "lack", "lady", "laid", "lake", "lamp", "land", "language", "large", "larger", "largest", "last", "late", "later", "laugh", "law", "lay", "layers", "lead", "leader", "leaf", "learn", "least", "leather", "leave", "leaving", "led", "left", "leg", "length", "lesson", "let", "letter", "level", "library", "lie", "life", "lift", "light", "like", "likely", "limited", "line", "lion", "lips", "liquid", "list", "listen", "little", "live", "living", "load", "local", "locate", "location", "log", "lonely", "long", "longer", "look", "loose", "lose", "loss", "lost", "lot", "loud", "love", "lovely", "low", "lower", "luck", "lucky", "lunch", "lungs", "lying", "machine", "machinery", "mad", "made", "magic", "magnet", "mail", "main", "mainly", "major", "make", "making", "man", "managed", "manner", "manufacturing", "many", "map", "mark", "market", "married", "mass", "massage", "master", "material", "mathematics", "matter", "may", "maybe", "me", "meal", "mean", "means", "meant", "measure", "meat", "medicine", "meet", "melted", "member", "memory", "men", "mental", "merely", "met", "metal", "method", "mice", "middle", "might", "mighty", "mile", "military", "milk", "mill", "mind", "mine", "minerals", "minute", "mirror", "missing", "mission", "mistake", "mix", "mixture", "model", "modern", "molecular", "moment", "money", "monkey", "month", "mood", "moon", "more", "morning", "most", "mostly", "mother", "motion", "motor", "mountain", "mouse", "mouth", "move", "movement", "movie", "moving", "mud", "muscle", "music", "musical", "must", "my", "myself", "mysterious", "nails", "name", "nation", "national", "native", "natural", "naturally", "nature", "near", "nearby", "nearer", "nearest", "nearly", "necessary", "neck", "needed", "needle", "needs", "negative", "neighbor", "neighborhood", "nervous", "nest", "never", "new", "news", "newspaper", "next", "nice", "night", "nine", "no", "nobody", "nodded", "noise", "none", "noon", "nor", "north", "nose", "not", "note", "noted", "nothing", "notice", "noun", "now", "number", "numeral", "nuts", "object", "observe", "obtain", "occasionally", "occur", "ocean", "of", "off", "offer", "office", "officer", "official", "oil", "old", "older", "oldest", "on", "once", "one", "only", "onto", "open", "operation", "opinion", "opportunity", "opposite", "or", "orange", "orbit", "order", "ordinary", "organization", "organized", "origin", "original", "other", "ought", "our", "ourselves", "out", "outer", "outline", "outside", "over", "own", "owner", "oxygen", "pack", "package", "page", "paid", "pain", "paint", "pair", "palace", "pale", "pan", "paper", "paragraph", "parallel", "parent", "park", "part", "particles", "particular", "particularly", "partly", "parts", "party", "pass", "passage", "past", "path", "pattern", "pay", "peace", "pen", "pencil", "people", "per", "percent", "perfect", "perfectly", "perhaps", "period", "person", "personal", "pet", "phrase", "physical", "piano", "pick", "picture", "pictured", "pie", "piece", "pig", "pile", "pilot", "pine", "pink", "pipe", "pitch", "place", "plain", "plan", "plane", "planet", "planned", "planning", "plant", "plastic", "plate", "plates", "play", "pleasant", "please", "pleasure", "plenty", "plural", "plus", "pocket", "poem", "poet", "poetry", "point", "pole", "police", "policeman", "political", "pond", "pony", "pool", "poor", "popular", "population", "porch", "port", "position", "positive", "possible", "possibly", "post", "pot", "potatoes", "pound", "pour", "powder", "power", "powerful", "practical", "practice", "prepare", "present", "president", "press", "pressure", "pretty", "prevent", "previous", "price", "pride", "primitive", "principal", "principle", "printed", "private", "prize", "probably", "problem", "process", "produce", "product", "production", "program", "progress", "promised", "proper", "properly", "property", "protection", "proud", "prove", "provide", "public", "pull", "pupil", "pure", "purple", "purpose", "push", "put", "putting", "quarter", "queen", "question", "quick", "quickly", "quiet", "quietly", "quite", "rabbit", "race", "radio", "railroad", "rain", "raise", "ran", "ranch", "range", "rapidly", "rate", "rather", "raw", "rays", "reach", "read", "reader", "ready", "real", "realize", "rear", "reason", "recall", "receive", "recent", "recently", "recognize", "record", "red", "refer", "refused", "region", "regular", "related", "relationship", "religious", "remain", "remarkable", "remember", "remove", "repeat", "replace", "replied", "report", "represent", "require", "research", "respect", "rest", "result", "return", "review", "rhyme", "rhythm", "rice", "rich", "ride", "riding", "right", "ring", "rise", "rising", "river", "road", "roar", "rock", "rocket", "rocky", "rod", "roll", "roof", "room", "root", "rope", "rose", "rough", "round", "route", "row", "rubbed", "rubber", "rule", "ruler", "run", "running", "rush", "sad", "saddle", "safe", "safety", "said", "sail", "sale", "salmon", "salt", "same", "sand", "sang", "sat", "satellites", "satisfied", "save", "saved", "saw", "say", "scale", "scared", "scene", "school", "science", "scientific", "scientist", "score", "screen", "sea", "search", "season", "seat", "second", "secret", "section", "see", "seed", "seeing", "seems", "seen", "seldom", "select", "selection", "sell", "send", "sense", "sent", "sentence", "separate", "series", "serious", "serve", "service", "sets", "setting", "settle", "settlers", "seven", "several", "shade", "shadow", "shake", "shaking", "shall", "shallow", "shape", "share", "sharp", "she", "sheep", "sheet", "shelf", "shells", "shelter", "shine", "shinning", "ship", "shirt", "shoe", "shoot", "shop", "shore", "short", "shorter", "shot", "should", "shoulder", "shout", "show", "shown", "shut", "sick", "sides", "sight", "sign", "signal", "silence", "silent", "silk", "silly", "silver", "similar", "simple", "simplest", "simply", "since", "sing", "single", "sink", "sister", "sit", "sitting", "situation", "six", "size", "skill", "skin", "sky", "slabs", "slave", "sleep", "slept", "slide", "slight", "slightly", "slip", "slipped", "slope", "slow", "slowly", "small", "smaller", "smallest", "smell", "smile", "smoke", "smooth", "snake", "snow", "so", "soap", "social", "society", "soft", "softly", "soil", "solar", "sold", "soldier", "solid", "solution", "solve", "some", "somebody", "somehow", "someone", "something", "sometime", "somewhere", "son", "song", "soon", "sort", "sound", "source", "south", "southern", "space", "speak", "special", "species", "specific", "speech", "speed", "spell", "spend", "spent", "spider", "spin", "spirit", "spite", "split", "spoken", "sport", "spread", "spring", "square", "stage", "stairs", "stand", "standard", "star", "stared", "start", "state", "statement", "station", "stay", "steady", "steam", "steel", "steep", "stems", "step", "stepped", "stick", "stiff", "still", "stock", "stomach", "stone", "stood", "stop", "stopped", "store", "storm", "story", "stove", "straight", "strange", "stranger", "straw", "stream", "street", "strength", "stretch", "strike", "string", "strip", "strong", "stronger", "struck", "structure", "struggle", "stuck", "student", "studied", "studying", "subject", "substance", "success", "successful", "such", "sudden", "suddenly", "sugar", "suggest", "suit", "sum", "summer", "sun", "sunlight", "supper", "supply", "support", "suppose", "sure", "surface", "surprise", "surrounded", "swam", "sweet", "swept", "swim", "swimming", "swing", "swung", "syllable", "symbol", "system", "table", "tail", "take", "taken", "tales", "talk", "tall", "tank", "tape", "task", "taste", "taught", "tax", "tea", "teach", "teacher", "team", "tears", "teeth", "telephone", "television", "tell", "temperature", "ten", "tent", "term", "terrible", "test", "than", "thank", "that", "thee", "them", "themselves", "then", "theory", "there", "therefore", "these", "they", "thick", "thin", "thing", "think", "third", "thirty", "this", "those", "thou", "though", "thought", "thousand", "thread", "three", "threw", "throat", "through", "throughout", "throw", "thrown", "thumb", "thus", "thy", "tide", "tie", "tight", "tightly", "till", "time", "tin", "tiny", "tip", "tired", "title", "to", "tobacco", "today", "together", "told", "tomorrow", "tone", "tongue", "tonight", "too", "took", "tool", "top", "topic", "torn", "total", "touch", "toward", "tower", "town", "toy", "trace", "track", "trade", "traffic", "trail", "train", "transportation", "trap", "travel", "treated", "tree", "triangle", "tribe", "trick", "tried", "trip", "troops", "tropical", "trouble", "truck", "trunk", "truth", "try", "tube", "tune", "turn", "twelve", "twenty", "twice", "two", "type", "typical", "uncle", "under", "underline", "understanding", "unhappy", "union", "unit", "universe", "unknown", "unless", "until", "unusual", "up", "upon", "upper", "upward", "us", "use", "useful", "using", "usual", "usually", "valley", "valuable", "value", "vapor", "variety", "various", "vast", "vegetable", "verb", "vertical", "very", "vessels", "victory", "view", "village", "visit", "visitor", "voice", "volume", "vote", "vowel", "voyage", "wagon", "wait", "walk", "wall", "want", "war", "warm", "warn", "was", "wash", "waste", "watch", "water", "wave", "way", "we", "weak", "wealth", "wear", "weather", "week", "weigh", "weight", "welcome", "well", "went", "were", "west", "western", "wet", "whale", "what", "whatever", "wheat", "wheel", "when", "whenever", "where", "wherever", "whether", "which", "while", "whispered", "whistle", "white", "who", "whole", "whom", "whose", "why", "wide", "widely", "wife", "wild", "will", "willing", "win", "wind", "window", "wing", "winter", "wire", "wise", "wish", "with", "within", "without", "wolf", "women", "won", "wonder", "wonderful", "wood", "wooden", "wool", "word", "wore", "work", "worker", "world", "worried", "worry", "worse", "worth", "would", "wrapped", "write", "writer", "writing", "written", "wrong", "wrote", "yard", "year", "yellow", "yes", "yesterday", "yet", "you", "young", "younger", "your", "yourself", "youth", "zero", "zebra", "zipper", "zoo", "zulu", ] const shortestWordSize = words.reduce((shortestWord, currentWord) => currentWord.length < shortestWord.length ? currentWord : shortestWord ).length const longestWordSize = words.reduce((longestWord, currentWord) => currentWord.length > longestWord.length ? currentWord : longestWord ).length export function randomWords( options: Readonly<{ amount: number; maxLength?: number; minLength?: number }> ): readonly string[] { const { amount, minLength, maxLength } = options function word(): string | undefined { let min = typeof minLength !== "number" ? shortestWordSize : limitWordSize(minLength) const max = typeof maxLength !== "number" ? longestWordSize : limitWordSize(maxLength) if (min > max) min = max let rightSize = false let wordUsed: string = "" while (!rightSize) { wordUsed = generateRandomWord() rightSize = wordUsed.length <= max && wordUsed.length >= min } return wordUsed } function generateRandomWord(): string { return words[randInt(words.length)] ?? "apple-tree" } // limits the size of words to the minimum and maximum possible function limitWordSize(wordSize: number) { if (wordSize < shortestWordSize) wordSize = shortestWordSize if (wordSize > longestWordSize) wordSize = longestWordSize return wordSize } function randInt(lessThan: number): number { return Math.floor(Math.random() * lessThan) } const results: string[] = [] let token = "" for (let i = 0; i < amount; i++) { token += word() results.push(token) token = "" } return results } ================================================ FILE: desktop/src/lib/releases.ts ================================================ import { useQuery } from "@tanstack/react-query" import { client } from "../client" import { Release } from "../gen" import { QueryKeys } from "../queryKeys" export function useReleases(): readonly Release[] | undefined { const { data: releases } = useQuery({ queryKey: QueryKeys.RELEASES, queryFn: async () => { return (await client.fetchReleases()).unwrap() }, }) return releases } ================================================ FILE: desktop/src/lib/result.ts ================================================ export class Err { readonly ok = false readonly err = true constructor(public readonly val: TError) {} public unwrap(): undefined { throw new Error(this.val.message, { cause: this.val }) } } export class Ok { readonly ok = true readonly err = false constructor(public readonly val: T) {} public unwrap(): T { return this.val } } // eslint-disable-next-line @typescript-eslint/naming-convention export type ResultError = Ok | Err // eslint-disable-next-line @typescript-eslint/naming-convention export type Result = Ok | Err // eslint-disable-next-line @typescript-eslint/naming-convention export type ErrorType = string export const ErrorTypeUnknown: ErrorType = "" export const ErrorTypeCancelled: ErrorType = "cancelled" // eslint-disable-next-line @typescript-eslint/no-unused-vars export const MapErrorCode = (code: number): ErrorType => { return ErrorTypeUnknown } export class Return { static Ok() { return new Ok(undefined) } static Value(val: TVal) { return new Ok(val) } static Failed(message: string, reason: string = "", type: ErrorType = ErrorTypeUnknown) { return new Err(new Failed(message, type, reason)) } static Error(val: TError): Err { return new Err(val) } } export class Failed extends Error { constructor( public readonly message: string, public readonly type: ErrorType = ErrorTypeUnknown, public readonly reason: string = "" ) { super(message) } } ================================================ FILE: desktop/src/lib/store.ts ================================================ import { LazyStore } from "@tauri-apps/plugin-store" import { TUnsubscribeFn } from "../types" import { EventManager } from "./eventManager" import { exists } from "./helpers" type TBaseStore = Record type TStore = Readonly<{ set(key: TKey, value: T[TKey]): Promise get(key: TKey): Promise remove(key: TKey): Promise subscribe(key: TKey, listener: (newValue: T[TKey]) => void): TUnsubscribeFn clear(): Promise }> type TStorageBackend = Omit, "subscribe"> export class Store implements TStore { private eventManager = new EventManager() constructor(private backend: TStorageBackend) {} public async set(key: TKey, value: T[TKey]): Promise { await this.backend.set(key, value) this.eventManager.publish(key, value) } public async get(key: TKey): Promise { return this.backend.get(key) } public async remove(key: TKey): Promise { return this.backend.remove(key) } public subscribe( key: TKey, listener: (newValue: T[TKey]) => void ): VoidFunction { const handler = EventManager.toHandler(listener) return this.eventManager.subscribe(key, handler) } public async clear(): Promise { return this.backend.clear() } } export class LocalStorageBackend implements TStorageBackend { constructor(private storageKey: string) {} private getKey(key: keyof TBaseStore): string { return `devpod-${this.storageKey}-${key.toString()}` } public async set(key: TKey, value: T[TKey]): Promise { try { window.localStorage.setItem(this.getKey(key), JSON.stringify(value)) } catch { // noop } } public async get(key: TKey): Promise { try { const maybeValue = window.localStorage.getItem(this.getKey(key)) if (!exists(maybeValue)) { return null } return JSON.parse(maybeValue) } catch { return null } } public async remove(key: TKey): Promise { window.localStorage.removeItem(this.getKey(key)) } public async clear(): Promise { window.localStorage.clear() } } export class FileStorageBackend implements TStorageBackend { private readonly store: LazyStore constructor(name: string) { const fileName = `.${name}.json` this.store = new LazyStore(fileName) } public async set(key: TKey, value: T[TKey]): Promise { try { await this.store.set(key.toString(), value) await this.store.save() } catch { // noop } } public async get(key: TKey): Promise { try { const maybeValue = await this.store.get(key.toString()) if (!exists(maybeValue)) { return null } return maybeValue } catch { return null } } public async remove(key: TKey): Promise { await this.store.delete(key.toString()) await this.store.save() } public async clear(): Promise { await this.store.clear() await this.store.save() } } export class LocalStorageToFileMigrationBackend implements TStorageBackend { private lsBackend: LocalStorageBackend private fsBackend: FileStorageBackend constructor(private storageKey: string) { this.lsBackend = new LocalStorageBackend(this.storageKey) this.fsBackend = new FileStorageBackend(this.storageKey) } public async set(key: TKey, value: T[TKey]): Promise { await this.fsBackend.set(key, value) // don't wait for removal to confirm try { this.lsBackend.remove(key) } catch { // noop } } public async get(key: TKey): Promise { const fsValue = await this.fsBackend.get(key) if (exists(fsValue)) { return fsValue } const lsValue = await this.lsBackend.get(key) if (exists(lsValue)) { await this.fsBackend.set(key, lsValue) return lsValue } return null } public async remove(key: TKey): Promise { await Promise.all([this.lsBackend.remove(key), this.fsBackend.remove(key)]) } public async clear(): Promise { await Promise.all([this.lsBackend.clear(), this.fsBackend.clear()]) } } ================================================ FILE: desktop/src/lib/systemInfo.ts ================================================ import { useQuery } from "@tanstack/react-query" import { client, TArch, TPlatform } from "../client" import { QueryKeys } from "../queryKeys" export function usePlatform(): TPlatform | undefined { const { data: platform } = useQuery({ queryKey: QueryKeys.PLATFORM, queryFn: () => client.fetchPlatform(), }) return platform } export function useArch(): TArch | undefined { const { data: arch } = useQuery({ queryKey: QueryKeys.ARCHITECTURE, queryFn: () => client.fetchArch(), }) return arch } type TSystemTheme = Awaited, null>> export function useSystemTheme(): TSystemTheme | undefined { const { data: systemTheme } = useQuery({ queryKey: QueryKeys.SYSTEM_THEME, queryFn: async () => { const t = await client.getSystemTheme() if (t === null) { throw new Error("System theme is not available") } return t }, }) return systemTheme } ================================================ FILE: desktop/src/lib/types.ts ================================================ export type TAction< TType extends string, TPayload extends unknown | undefined = undefined, > = TPayload extends undefined ? Readonly<{ type: TType }> : { type: TType payload: TPayload } const PRO_INSTANCE_DETAILS = ["logs", "configuration"] as const export type TProInstanceDetail = (typeof PRO_INSTANCE_DETAILS)[number] ================================================ FILE: desktop/src/lib/useDownloadLogs.ts ================================================ import { client } from "@/client" import { TActionID } from "@/contexts" import { useToast } from "@chakra-ui/react" import { useMutation } from "@tanstack/react-query" import * as dialog from "@tauri-apps/plugin-dialog" export function useDownloadLogs() { const toast = useToast() const { mutate, isLoading: isDownloading } = useMutation({ mutationFn: async ({ actionID }: { actionID: TActionID }) => { const actionLogFile = (await client.workspaces.getActionLogFile(actionID)).unwrap() if (actionLogFile === undefined) { throw new Error(`Unable to retrieve file for action ${actionID}`) } const targetFile = await dialog.save({ title: "Save Logs", filters: [{ name: "format", extensions: ["log", "txt"] }], }) // user cancelled "save file" dialog if (targetFile === null) { return } await client.copyFile(actionLogFile, targetFile) client.open(targetFile) }, onError(error) { toast({ title: `Failed to save logs: ${error}`, status: "error", isClosable: true, duration: 30_000, // 30 sec }) }, }) return { download: mutate, isDownloading } } ================================================ FILE: desktop/src/lib/useFormErrors.ts ================================================ import { useMemo } from "react" import { FieldError, FieldValues, FormState } from "react-hook-form" type TErr = `${TPrefix}Error` type TFormErrors> = { [K in keyof T as TErr]?: FieldError } export function useFormErrors( fieldNames: readonly (keyof TFormValues)[], formState: FormState ): TFormErrors { return useMemo(() => { return fieldNames.reduce>( (acc, curr) => ({ ...acc, [`${String(curr)}Error`]: formState.errors[curr] }), {} ) }, [fieldNames, formState.errors]) } ================================================ FILE: desktop/src/lib/useHover.ts ================================================ import { LegacyRef, useEffect, useRef, useState } from "react" export function useHover(): [boolean, LegacyRef] { const [isHovering, setIsHovering] = useState(false) const ref = useRef(null) useEffect( () => { const handleMouseOver = () => setIsHovering(true) const handleMouseOut = () => setIsHovering(false) setTimeout(() => { const node = ref.current if (node) { node.addEventListener("mouseover", handleMouseOver) node.addEventListener("mouseout", handleMouseOut) return () => { node.removeEventListener("mouseover", handleMouseOver) node.removeEventListener("mouseout", handleMouseOut) } } }) }, // rerun if ref changes! // eslint-disable-next-line react-hooks/exhaustive-deps [ref.current] ) return [isHovering, ref] } ================================================ FILE: desktop/src/lib/useSelection.ts ================================================ import { useCallback, useMemo, useState } from "react" export function useSelection() { const [selectedItems, setSelectedItems] = useState(new Set()) const toggleSelection = useCallback( (id: TIdType) => { setSelectedItems((curr) => { const updated = new Set(curr) if (updated.has(id)) { updated.delete(id) } else { updated.add(id) } return updated }) }, [setSelectedItems] ) const toggleSelectAll = useCallback( (allSet: TIdType[]) => { setSelectedItems((curr) => { if (curr.size === allSet.length) { return new Set() } return new Set(allSet) }) }, [setSelectedItems] ) const setSelected = useCallback( (id: TIdType, selected: boolean) => { if (!selectedItems.has(id) && selected) { const updated = new Set(selectedItems) updated.add(id) setSelectedItems(updated) } else if (selectedItems.has(id) && !selected) { const updated = new Set(selectedItems) updated.delete(id) setSelectedItems(updated) } }, [setSelectedItems, selectedItems] ) // Will remove outdated selection items if they are no longer part of the entire set. const prune = useCallback( (allSet: TIdType[]) => { const updated = new Set(selectedItems) let changed = false for (const id of selectedItems) { if (!allSet.includes(id)) { updated.delete(id) changed = true } } if (changed) { setSelectedItems(updated) } }, [setSelectedItems, selectedItems] ) const clear = useCallback(() => { setSelectedItems(new Set()) }, [setSelectedItems]) const has = useCallback( (id: TIdType) => { return selectedItems.has(id) }, [selectedItems] ) return useMemo( () => ({ toggleSelection, toggleSelectAll, size: selectedItems.size, clear, prune, setSelected, has, selectedItems: { val: selectedItems, set: setSelectedItems, }, }), [ selectedItems, setSelectedItems, toggleSelection, toggleSelectAll, clear, prune, has, setSelected, ] ) } ================================================ FILE: desktop/src/lib/useSortWorkspaces.tsx ================================================ import { TWorkspace } from "@/types" import { useMemo } from "react" import { ProWorkspaceInstance } from "@/contexts" import { getLastActivity } from "@/lib/pro" export enum ESortWorkspaceMode { RECENTLY_USED = "Recently Used", LEAST_RECENTLY_USED = "Least Recently Used", RECENTLY_CREATED = "Recently Created", LEAST_RECENTLY_CREATED = "Least Recently Created", } export const DEFAULT_SORT_WORKSPACE_MODE = ESortWorkspaceMode.RECENTLY_USED type TSortable = { original: TOriginal created: number used: number } function sortWorkspaces( sortables: TSortable[], sortMode: ESortWorkspaceMode | undefined ): T[] { const copy = [...sortables] copy.sort((a, b) => { if (sortMode === ESortWorkspaceMode.RECENTLY_USED) { return a.used > b.used ? -1 : 1 } if (sortMode === ESortWorkspaceMode.LEAST_RECENTLY_USED) { return b.used > a.used ? -1 : 1 } if (sortMode === ESortWorkspaceMode.RECENTLY_CREATED) { return a.created > b.created ? -1 : 1 } if (sortMode === ESortWorkspaceMode.LEAST_RECENTLY_CREATED) { return b.created > a.created ? -1 : 1 } return 0 }) return copy.map((copy) => copy.original) } export function useSortWorkspaces( workspaces: readonly TWorkspace[] | undefined, sortMode: ESortWorkspaceMode | undefined ) { return useMemo(() => { if (!workspaces) { return undefined } const sortables = workspaces.map((workspace) => ({ original: workspace, created: new Date(workspace.creationTimestamp).getTime(), used: new Date(workspace.lastUsed).getTime(), })) return sortWorkspaces(sortables, sortMode) }, [workspaces, sortMode]) } export function useSortProWorkspaces( workspaces: readonly ProWorkspaceInstance[] | undefined, sortMode: ESortWorkspaceMode | undefined ) { return useMemo(() => { if (!workspaces) { return undefined } const sortables = workspaces.map((workspace) => ({ original: workspace, created: new Date(workspace.metadata?.creationTimestamp ?? 0).getTime(), used: getLastActivity(workspace)?.getTime() ?? 0, })) return sortWorkspaces(sortables, sortMode) }, [workspaces, sortMode]) } ================================================ FILE: desktop/src/lib/useStoreTroubleshoot.ts ================================================ import { client } from "@/client" import { TActionObj } from "@/contexts/DevPodContext/action" import { TWorkspace } from "@/types" import { useToast } from "@chakra-ui/react" import { useMutation } from "@tanstack/react-query" import { ProWorkspaceInstance } from "@/contexts" import JSZip from "jszip" export function useStoreTroubleshoot() { const toast = useToast() const { mutate, isLoading: isStoring } = useMutation({ mutationFn: async ({ workspace, workspaceActions, }: { workspace: TWorkspace | ProWorkspaceInstance workspaceActions: TActionObj[] }) => { const logFiles = await Promise.all( workspaceActions.map((action) => client.workspaces.getActionLogFile(action.id)) ) const targetFolder = await client.selectFromDir("Save Troubleshooting Data") // user cancelled "save file" dialog if (targetFolder === null) { return } const unwrappedLogFiles: [src: [string], targetFolder: string][] = logFiles .filter((f) => f.ok) .map((f) => f.unwrap() ?? "") .map((f) => [[f], f.split(client.pathSeparator()).pop() ?? ""]) const zip = new JSZip() const logFilesData = ( await Promise.all( unwrappedLogFiles.map(async ([src, target]) => { try { const data = await client.readFile(src) return { fileName: target, data } } catch { // ignore missing log files and continue return null } }) ) ).filter((d): d is Exclude => d != null) logFilesData.forEach((logFile) => { zip.file(logFile.fileName, logFile.data) }) zip.file("workspace_actions.json", JSON.stringify(workspaceActions, null, 2)) zip.file("workspace.json", JSON.stringify(workspace, null, 2)) const troubleshootOutput = await client.workspaces.troubleshoot({ id: workspace.id, actionID: "", streamID: "", }) if (troubleshootOutput.ok) { zip.file("cli_troubleshoot.json", troubleshootOutput.unwrap().stdout) } const out = await zip.generateAsync({ type: "uint8array" }) await client.writeFile([targetFolder, "devpod_troubleshoot.zip"], out) client.open(targetFolder) }, onError(error) { toast({ title: `Failed to save zip: ${error}`, status: "error", isClosable: true, duration: 30_000, // 30 sec }) }, }) return { store: mutate, isStoring } } ================================================ FILE: desktop/src/lib/useUpdate.ts ================================================ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { useCallback, useState } from "react" import { client } from "../client" import { QueryKeys } from "../queryKeys" export function useUpdate() { const queryClient = useQueryClient() const { data: isUpdateAvailable, refetch, // Need to use `isFetching` as a workaround for interaction with `enabled: false` isFetching: isChecking, } = useQuery({ queryKey: QueryKeys.UPDATE_RELEASE, queryFn: async () => (await client.checkUpdates()).unwrap(), enabled: false, onSettled: () => { queryClient.resetQueries(QueryKeys.PENDING_UPDATE) }, }) const { data: pendingUpdate } = useQuery({ queryKey: QueryKeys.PENDING_UPDATE, queryFn: async () => (await client.fetchPendingUpdate()).unwrap(), enabled: false, }) const { mutate: installMutate, isLoading: isInstalling } = useMutation({ mutationKey: QueryKeys.INSTALL_UPDATE, mutationFn: async () => (await client.installUpdate()).unwrap(), onSettled: () => { queryClient.resetQueries(QueryKeys.PENDING_UPDATE) }, }) const [isInstallDisabled, setIsInstallDisabled] = useState(false) const install = useCallback(() => { if (isInstallDisabled) { return } installMutate(undefined, { onError: () => setIsInstallDisabled(false), onSuccess: () => setIsInstallDisabled(true), }) }, [installMutate, isInstallDisabled]) return { check: refetch, isUpdateAvailable, isChecking, pendingUpdate, install, isInstalling, isInstallDisabled, } } ================================================ FILE: desktop/src/lib/useVersion.ts ================================================ import { useQuery } from "@tanstack/react-query" import { client } from "../client" import { QueryKeys } from "../queryKeys" export function useVersion(): string | undefined { const { data: version } = useQuery({ queryKey: QueryKeys.APP_VERSION, queryFn: () => client.fetchVersion(), cacheTime: Infinity, staleTime: Infinity, }) return version } ================================================ FILE: desktop/src/main.tsx ================================================ import { Logger, QueryClient, QueryClientProvider } from "@tanstack/react-query" import { ReactQueryDevtools } from "@tanstack/react-query-devtools" import dayjs from "dayjs" import relativeTime from "dayjs/plugin/relativeTime" import { StrictMode } from "react" import ReactDOM from "react-dom/client" import { Location, RouterProvider } from "react-router" import "@xterm/xterm/css/xterm.css" import { ThemeProvider } from "./Theme" import { SettingsProvider } from "./contexts" import { router } from "./routes" import { client } from "./client" import { ColorModeScript } from "@chakra-ui/react" dayjs.extend(relativeTime) const logger: Logger | undefined = import.meta.env.PROD ? { log: () => { // noop in prod }, warn: (...args: any[]) => { client.log("warn", args.join(" ")) }, error: (...args: any[]) => { client.log("error", args.join(" ")) }, } : undefined const queryClient = new QueryClient({ logger }) let render = true const l = localStorage.getItem("devpod-location-current") // check usePreserveLocation before changing this if (l) { const loc = JSON.parse(l) as Location if (window.location.pathname !== loc.pathname) { window.location.href = loc.pathname + loc.search render = false } } if (render) { ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render() } // force chakra to determine color mode on startup localStorage.removeItem("chakra-ui-color-mode") function Root() { return ( {/* Will be disabled in production automatically */} ) } ================================================ FILE: desktop/src/queryKeys.ts ================================================ import { TProInstances, TProviderID, TWorkspaceID } from "./types" export const QueryKeys = { PLATFORM: ["platform"], ARCHITECTURE: ["architecture"], SYSTEM_THEME: ["systemTheme"], WORKSPACES: ["workspaces"], PROVIDERS: ["providers"], PROVIDERS_CHECK_UPDATE_ALL: ["providers", "update", "all"], IDES: ["ides"], COMMUNITY_CONTRIBUTIONS: ["communityContributions"], CONTEXT_OPTIONS: ["contextOptions"], RELEASES: ["releases"], APP_VERSION: ["appVersion"], UPDATE_RELEASE: ["updateRelease"], PENDING_UPDATE: ["pendingUpdate"], INSTALL_UPDATE: ["installUpdate"], PRO_INSTANCES: ["proInstances"], workspace(id: TWorkspaceID): string[] { return [...QueryKeys.WORKSPACES, id] }, workspaceStatus(id: TWorkspaceID): string[] { return [...QueryKeys.WORKSPACES, id, "status"] }, provider(id: TProviderID): string[] { return [...QueryKeys.PROVIDERS, id] }, IS_CLI_INSTALLED: ["isCliInstalled"], providerOptions(id: TProviderID): string[] { return [...QueryKeys.provider(id), "options"] }, providerSetOptions(id: TProviderID): string[] { return [...QueryKeys.provider(id), "set-options"] }, providerUpdate(id: TProviderID): string[] { return [...QueryKeys.provider(id), "update"] }, proWorkspaceTemplates(host: string, project: string): string[] { return ["workspaceTemplates", host, project] }, proClusters(host: string, project: string): string[] { return ["clusters", host, project] }, connectionStatus(host: string): string[] { return ["connectionStatus", host] }, versionInfo(host: string): string[] { return ["versionInfo", host] }, proProviderUpdates(proInstances: TProInstances | undefined) { return ["check-pro-provider-updates", proInstances] }, userProfile(name: string | undefined) { return ["user-profile", name] }, } export const MutationKeys = { CREATE_WORKSPACE: ["createWorkspace"], START_WORKSPACE: ["startWorkspace"], STOP_WORKSPACE: ["stopWorkspace"], REBUILD_WORKSPACE: ["rebuildWorkspace"], REMOVE_WORKSPACE: ["removeWorkspace"], } as const ================================================ FILE: desktop/src/routes.tsx ================================================ import { Params, Path, createBrowserRouter } from "react-router-dom" import { App, ErrorPage } from "./App" import { ProRoot } from "./ProRoot" import { TActionID } from "./contexts" import { TProInstanceDetail, exists } from "./lib" import { TProviderID, TSupportedIDE, TWorkspaceID } from "./types" import { Actions, Pro, Providers, Settings, Workspaces } from "./views" export const Routes = { ROOT: "/", SETTINGS: "/settings", WORKSPACES: "/workspaces", ACTIONS: "/actions", get ACTION(): string { return `${Routes.ACTIONS}/:action` }, get WORKSPACE_CREATE(): string { return `${Routes.WORKSPACES}/new` }, toWorkspaceCreate( options: Readonly<{ workspaceID: TWorkspaceID | null providerID: TProviderID | null ide: string | null rawSource: string | null }> ): Partial { const searchParams = new URLSearchParams() for (const [key, value] of Object.entries(options)) { if (exists(value)) { searchParams.set(key, value) } } return { pathname: Routes.WORKSPACE_CREATE, search: searchParams.toString(), } }, toAction(actionID: TActionID, onSuccess?: string): string { if (onSuccess) { return `${Routes.ACTIONS}/${actionID}?onSuccess=${encodeURIComponent(onSuccess)}` } return `${Routes.ACTIONS}/${actionID}` }, getActionID(params: Params): string | undefined { // Needs to match `:action` from detail route exactly! return params["action"] }, getWorkspaceCreateParamsFromSearchParams(searchParams: URLSearchParams): Partial< Readonly<{ workspaceID: TWorkspaceID providerID: TProviderID ide: TSupportedIDE rawSource: string }> > { return { workspaceID: searchParams.get("workspaceID") ?? undefined, providerID: searchParams.get("providerID") ?? undefined, ide: (searchParams.get("ide") as TSupportedIDE | null) ?? undefined, rawSource: searchParams.get("rawSource") ?? undefined, } }, PROVIDERS: "/providers", get PROVIDER(): string { return `${Routes.PROVIDERS}/:provider` }, toProvider(providerID: string): string { return `${Routes.PROVIDERS}/${providerID}` }, getProviderId(params: Params): string | undefined { // Needs to match `:provider` from detail route exactly! return params["provider"] }, PRO: "/pro", PRO_INSTANCE: "/pro/:host", PRO_WORKSPACE: "/pro/:host/:workspace", PRO_WORKSPACE_SELECT_PRESET: "/pro/:host/select-preset", PRO_WORKSPACE_CREATE: "/pro/:host/new", PRO_SETTINGS: "/pro/:host/user/settings", PRO_CREDENTIALS: "/pro/:host/user/credentials", PRO_PROFILE: "/pro/:host/user/profile", toProInstance(host: string): string { return `/pro/${host}` }, toProWorkspace(host: string, instanceID: string): string { const base = this.toProInstance(host) return `${base}/${instanceID}` }, toProWorkspaceCreate(host: string, fromPreset?: string): string { const base = this.toProInstance(host) return `${base}/new${fromPreset ? `?fromPreset=${fromPreset}` : ""}` }, toProSelectPreset(host: string): string { const base = this.toProInstance(host) return `${base}/select-preset` }, toProWorkspaceDetail(host: string, instanceID: string, detail: TProInstanceDetail): string { const base = this.toProInstance(host) return `${base}/${instanceID}?tab=${detail}` }, toProSettings(host: string): string { const base = this.toProInstance(host) return `${base}/user/settings` }, toProCredentials(host: string): string { const base = this.toProInstance(host) return `${base}/user/credentials` }, toProProfile(host: string): string { const base = this.toProInstance(host) return `${base}/user/profile` }, getProWorkspaceDetailsParams( searchParams: URLSearchParams ): Partial> { return { tab: searchParams.get("tab") as TProInstanceDetail | null } }, } as const export const router = createBrowserRouter([ { path: Routes.ROOT, element: , errorElement: , children: [ { path: Routes.PRO, element: , children: [ { path: Routes.PRO_INSTANCE, element: , children: [ { index: true, element: , }, { path: Routes.PRO_WORKSPACE, element: , }, { path: Routes.PRO_WORKSPACE_CREATE, element: , }, { path: Routes.PRO_WORKSPACE_SELECT_PRESET, element: , }, { path: Routes.PRO_SETTINGS, element: }, { path: Routes.PRO_CREDENTIALS, element: }, { path: Routes.PRO_PROFILE, element: }, ], }, ], }, { path: Routes.WORKSPACES, element: , children: [ { index: true, element: , }, { path: Routes.WORKSPACE_CREATE, element: , }, ], }, { path: Routes.PROVIDERS, element: , children: [ { index: true, element: }, { path: Routes.PROVIDER, element: , }, ], }, { path: Routes.ACTIONS, element: , children: [{ path: Routes.ACTION, element: }], }, { path: Routes.SETTINGS, element: }, ], }, ]) ================================================ FILE: desktop/src/runner.ts ================================================ import { V1Affinity } from "@loft-enterprise/client/gen/models/V1Affinity" import { V1Container } from "@loft-enterprise/client/gen/models/V1Container" import { V1EnvFromSource } from "@loft-enterprise/client/gen/models/V1EnvFromSource" import { V1EnvVar } from "@loft-enterprise/client/gen/models/V1EnvVar" import { V1HostAlias } from "@loft-enterprise/client/gen/models/V1HostAlias" import { V1ObjectMeta } from "@loft-enterprise/client/gen/models/V1ObjectMeta" import { V1ResourceRequirements } from "@loft-enterprise/client/gen/models/V1ResourceRequirements" import { V1Toleration } from "@loft-enterprise/client/gen/models/V1Toleration" import { V1Volume } from "@loft-enterprise/client/gen/models/V1Volume" import { V1VolumeMount } from "@loft-enterprise/client/gen/models/V1VolumeMount" import { StorageV1Condition } from "@loft-enterprise/client/gen/models/agentstorageV1Condition" import { StorageV1Access } from "@loft-enterprise/client/gen/models/storageV1Access" import { StorageV1TemplateMetadata } from "@loft-enterprise/client/gen/models/storageV1TemplateMetadata" import { StorageV1UserOrTeam } from "@loft-enterprise/client/gen/models/storageV1UserOrTeam" /** * Runner holds the Runner information */ export class ManagementV1Runner { /** * APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources */ "apiVersion"?: string /** * Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds */ "kind"?: string "metadata"?: V1ObjectMeta "spec"?: ManagementV1RunnerSpec "status"?: ManagementV1RunnerStatus static readonly discriminator: string | undefined = undefined static readonly attributeTypeMap: Array<{ name: string baseName: string type: string format: string }> = [ { name: "apiVersion", baseName: "apiVersion", type: "string", format: "", }, { name: "kind", baseName: "kind", type: "string", format: "", }, { name: "metadata", baseName: "metadata", type: "V1ObjectMeta", format: "", }, { name: "spec", baseName: "spec", type: "ManagementV1RunnerSpec", format: "", }, { name: "status", baseName: "status", type: "ManagementV1RunnerStatus", format: "", }, ] static getAttributeTypeMap() { return ManagementV1Runner.attributeTypeMap } public constructor() {} } /** * RunnerSpec holds the specification */ export class ManagementV1RunnerSpec { /** * Access holds the access rights for users and teams */ "access"?: Array "clusterRef"?: StorageV1RunnerClusterRef /** * Description describes a cluster access object */ "description"?: string /** * The display name shown in the UI */ "displayName"?: string /** * Endpoint is the hostname used to connect directly to the runner */ "endpoint"?: string /** * NetworkPeerName is the network peer name used to connect directly to the runner */ "networkPeerName"?: string "owner"?: StorageV1UserOrTeam /** * If unusable is true, no DevPod workspaces can be scheduled on this runner. */ "unusable"?: boolean static readonly discriminator: string | undefined = undefined static readonly attributeTypeMap: Array<{ name: string baseName: string type: string format: string }> = [ { name: "access", baseName: "access", type: "Array", format: "", }, { name: "clusterRef", baseName: "clusterRef", type: "StorageV1RunnerClusterRef", format: "", }, { name: "description", baseName: "description", type: "string", format: "", }, { name: "displayName", baseName: "displayName", type: "string", format: "", }, { name: "endpoint", baseName: "endpoint", type: "string", format: "", }, { name: "networkPeerName", baseName: "networkPeerName", type: "string", format: "", }, { name: "owner", baseName: "owner", type: "StorageV1UserOrTeam", format: "", }, { name: "unusable", baseName: "unusable", type: "boolean", format: "", }, ] static getAttributeTypeMap() { return ManagementV1RunnerSpec.attributeTypeMap } public constructor() {} } /** * RunnerStatus holds the status */ export class ManagementV1RunnerStatus { /** * Conditions holds several conditions the virtual cluster might be in */ "conditions"?: Array /** * Message describes the reason in human-readable form */ "message"?: string /** * Phase describes the current phase the space instance is in */ "phase"?: string /** * Reason describes the reason in machine-readable form */ "reason"?: string static readonly discriminator: string | undefined = undefined static readonly attributeTypeMap: Array<{ name: string baseName: string type: string format: string }> = [ { name: "conditions", baseName: "conditions", type: "Array", format: "", }, { name: "message", baseName: "message", type: "string", format: "", }, { name: "phase", baseName: "phase", type: "string", format: "", }, { name: "reason", baseName: "reason", type: "string", format: "", }, ] static getAttributeTypeMap() { return ManagementV1RunnerStatus.attributeTypeMap } public constructor() {} } export class StorageV1RunnerClusterRef { /** * Cluster is the connected cluster the space will be created in */ "cluster"?: string /** * Namespace is the namespace inside the connected cluster holding the space */ "namespace"?: string "persistentVolumeClaimTemplate"?: StorageV1RunnerPersistentVolumeClaimTemplate "podTemplate"?: StorageV1RunnerPodTemplate "serviceTemplate"?: StorageV1RunnerServiceTemplate static readonly discriminator: string | undefined = undefined static readonly attributeTypeMap: Array<{ name: string baseName: string type: string format: string }> = [ { name: "cluster", baseName: "cluster", type: "string", format: "", }, { name: "namespace", baseName: "namespace", type: "string", format: "", }, { name: "persistentVolumeClaimTemplate", baseName: "persistentVolumeClaimTemplate", type: "StorageV1RunnerPersistentVolumeClaimTemplate", format: "", }, { name: "podTemplate", baseName: "podTemplate", type: "StorageV1RunnerPodTemplate", format: "", }, { name: "serviceTemplate", baseName: "serviceTemplate", type: "StorageV1RunnerServiceTemplate", format: "", }, ] static getAttributeTypeMap() { return StorageV1RunnerClusterRef.attributeTypeMap } public constructor() {} } export class StorageV1RunnerPodTemplate { "metadata"?: StorageV1TemplateMetadata "spec"?: StorageV1RunnerPodTemplateSpec static readonly discriminator: string | undefined = undefined static readonly attributeTypeMap: Array<{ name: string baseName: string type: string format: string }> = [ { name: "metadata", baseName: "metadata", type: "StorageV1TemplateMetadata", format: "", }, { name: "spec", baseName: "spec", type: "StorageV1RunnerPodTemplateSpec", format: "", }, ] static getAttributeTypeMap() { return StorageV1RunnerPodTemplate.attributeTypeMap } public constructor() {} } export class StorageV1RunnerPersistentVolumeClaimTemplateSpec { /** * accessModes contains the desired access modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1 */ "accessModes"?: Array /** * storageClassName is the name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1 */ "storageClassName"?: string /** * storageSize is the size of the storage to reserve for the pvc */ "storageSize"?: string static readonly discriminator: string | undefined = undefined static readonly attributeTypeMap: Array<{ name: string baseName: string type: string format: string }> = [ { name: "accessModes", baseName: "accessModes", type: "Array", format: "", }, { name: "storageClassName", baseName: "storageClassName", type: "string", format: "", }, { name: "storageSize", baseName: "storageSize", type: "string", format: "", }, ] static getAttributeTypeMap() { return StorageV1RunnerPersistentVolumeClaimTemplateSpec.attributeTypeMap } public constructor() {} } export enum StorageV1RunnerPersistentVolumeClaimTemplateSpecAccessModesEnum { ReadOnlyMany = "ReadOnlyMany", ReadWriteMany = "ReadWriteMany", ReadWriteOnce = "ReadWriteOnce", ReadWriteOncePod = "ReadWriteOncePod", } export class StorageV1RunnerPodTemplateSpec { "affinity"?: V1Affinity /** * List of environment variables to set in the container. Cannot be updated. */ "env"?: Array /** * List of sources to populate environment variables in the container. The keys defined within a source must be a C_IDENTIFIER. All invalid keys will be reported as an event when the container is starting. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated. */ "envFrom"?: Array /** * Set host aliases for the Runner Pod */ "hostAliases"?: Array /** * Runner pod image to use other than default */ "image"?: string /** * Set up Init Containers for the Runner */ "initContainers"?: Array /** * Set the NodeSelector for the Runner Pod */ "nodeSelector"?: { [key: string]: string } "resource"?: V1ResourceRequirements /** * Set the Tolerations for the Runner Pod */ "tolerations"?: Array /** * Set Volume Mounts for the Runner Pod */ "volumeMounts"?: Array /** * Set Volumes for the Runner Pod */ "volumes"?: Array static readonly discriminator: string | undefined = undefined static readonly attributeTypeMap: Array<{ name: string baseName: string type: string format: string }> = [ { name: "affinity", baseName: "affinity", type: "V1Affinity", format: "", }, { name: "env", baseName: "env", type: "Array", format: "", }, { name: "envFrom", baseName: "envFrom", type: "Array", format: "", }, { name: "hostAliases", baseName: "hostAliases", type: "Array", format: "", }, { name: "image", baseName: "image", type: "string", format: "", }, { name: "initContainers", baseName: "initContainers", type: "Array", format: "", }, { name: "nodeSelector", baseName: "nodeSelector", type: "{ [key: string]: string; }", format: "", }, { name: "resource", baseName: "resource", type: "V1ResourceRequirements", format: "", }, { name: "tolerations", baseName: "tolerations", type: "Array", format: "", }, { name: "volumeMounts", baseName: "volumeMounts", type: "Array", format: "", }, { name: "volumes", baseName: "volumes", type: "Array", format: "", }, ] static getAttributeTypeMap() { return StorageV1RunnerPodTemplateSpec.attributeTypeMap } public constructor() {} } export class StorageV1RunnerPersistentVolumeClaimTemplate { "metadata"?: StorageV1TemplateMetadata "spec"?: StorageV1RunnerPersistentVolumeClaimTemplateSpec static readonly discriminator: string | undefined = undefined static readonly attributeTypeMap: Array<{ name: string baseName: string type: string format: string }> = [ { name: "metadata", baseName: "metadata", type: "StorageV1TemplateMetadata", format: "", }, { name: "spec", baseName: "spec", type: "StorageV1RunnerPersistentVolumeClaimTemplateSpec", format: "", }, ] static getAttributeTypeMap() { return StorageV1RunnerPersistentVolumeClaimTemplate.attributeTypeMap } public constructor() {} } export class StorageV1RunnerServiceTemplate { "metadata"?: StorageV1TemplateMetadata "spec"?: StorageV1RunnerServiceTemplateSpec static readonly discriminator: string | undefined = undefined static readonly attributeTypeMap: Array<{ name: string baseName: string type: string format: string }> = [ { name: "metadata", baseName: "metadata", type: "StorageV1TemplateMetadata", format: "", }, { name: "spec", baseName: "spec", type: "StorageV1RunnerServiceTemplateSpec", format: "", }, ] static getAttributeTypeMap() { return StorageV1RunnerServiceTemplate.attributeTypeMap } public constructor() {} } export class StorageV1RunnerServiceTemplateSpec { /** * type determines how the Service is exposed. Defaults to ClusterIP Possible enum values: - `\"ClusterIP\"` means a service will only be accessible inside the cluster, via the cluster IP. - `\"ExternalName\"` means a service consists of only a reference to an external name that kubedns or equivalent will return as a CNAME record, with no exposing or proxying of any pods involved. - `\"LoadBalancer\"` means a service will be exposed via an external load balancer (if the cloud provider supports it), in addition to \'NodePort\' type. - `\"NodePort\"` means a service will be exposed on one port of every node, in addition to \'ClusterIP\' type. */ "type"?: StorageV1RunnerServiceTemplateSpecTypeEnum static readonly discriminator: string | undefined = undefined static readonly attributeTypeMap: Array<{ name: string baseName: string type: string format: string }> = [ { name: "type", baseName: "type", type: "StorageV1RunnerServiceTemplateSpecTypeEnum", format: "", }, ] static getAttributeTypeMap() { return StorageV1RunnerServiceTemplateSpec.attributeTypeMap } public constructor() {} } export enum StorageV1RunnerServiceTemplateSpecTypeEnum { ClusterIp = "ClusterIP", ExternalName = "ExternalName", LoadBalancer = "LoadBalancer", NodePort = "NodePort", } ================================================ FILE: desktop/src/types.ts ================================================ import { UseMutationResult } from "@tanstack/react-query" import { TStreamEventListenerFn } from "./client" type TMaybe = T | null | undefined export type TUnsubscribeFn = VoidFunction export type TComparable = Readonly<{ eq(b: T): boolean }> export type TIdentifiable = Readonly<{ id: string }> export type TStreamID = string export type TDeepNonNullable = { [K in keyof T]-?: T[K] extends object ? TDeepNonNullable : Required> } //#region Shared export type TLogOutput = Readonly<{ time: Date; message?: string; level: string }> export type TQueryResult> = [ TData | undefined, Pick, ] export type TRunnable = Readonly<{ run(config: TRunConfig): void }> //#endregion //#region IDE export type TIDEs = readonly TIDE[] export type TIDE = Readonly<{ name: TMaybe displayName: string default: TMaybe icon: TMaybe iconDark: TMaybe experimental: TMaybe group: TMaybe<"Primary" | "JetBrains" | "Other"> }> //#endregion //#region Provider export type TProviderID = string export type TOptionID = string export type TWithProviderID = Readonly<{ providerID: TProviderID }> export type TProviders = Record export type TProvider = Readonly<{ config: TMaybe default: TMaybe state: TMaybe< Readonly<{ initialized: TMaybe singleMachine: TMaybe creationTimestamp: TMaybe options: TMaybe }> > }> & { isProxyProvider: boolean } export type TNamedProvider = TProvider & Readonly<{ name: string }> export type TProviderConfig = Readonly<{ name: TMaybe version: TMaybe source: TMaybe description: TMaybe optionGroups: TProviderOptionGroup[] options: TProviderOptions icon: TMaybe home: TMaybe exec: | TMaybe & { proxy: never; daemon: never }> | TMaybe<{ proxy: TMaybe>; daemon: never }> | TMaybe<{ daemon: TMaybe>; proxy: never }> }> export type TProviderOptionGroup = Readonly<{ name: TMaybe options: TMaybe defaultVisible: TMaybe }> export type TProviderSource = Readonly<{ internal: TMaybe github: TMaybe file: TMaybe url: TMaybe raw: TMaybe }> export type TProviderOptions = Record export type TProviderOption = Readonly<{ // Value is the options current value value: TMaybe // Children are the children generated by this option children: TMaybe // If value is a password password: TMaybe // DisplayName of the option, preferred over the option name by a supporting tool. displayName: TMaybe // A description of the option displayed to the user by a supporting tool. description: TMaybe // If required is true and the user doesn't supply a value, devpod will ask the user required: TMaybe // Allowed values for this option. enum: TMaybe // Suggestions are suggestions to show in the DevPod UI for this option suggestions: TMaybe // Hidden specifies if the option should be hidden hidden: TMaybe // Local means the variable is not resolved immediately and instead later when the workspace / machine was created. local: TMaybe // Default value if the user omits this option from their configuration. default: TMaybe // Command is the command to run to specify an option command: TMaybe // SubOptionsCommand is the command to retrieve sub options subOptionsCommand: TMaybe // Type is the provider option type. Can be one of: string, multiline, duration, number or boolean. Defaults to string type: TMaybe<"string" | "duration" | "number" | "boolean" | "multiline"> // Mutable specifies if an option can be changed on the workspace or machine after creating it mutable: TMaybe }> export type TOptionEnum = Readonly<{ value: TMaybe displayName: TMaybe }> export type TAddProviderConfig = Readonly<{ name?: TProviderConfig["name"] }> export type TConfigureProviderConfig = Readonly<{ options: Record useAsDefaultProvider?: boolean reuseMachine?: boolean reconfigure?: boolean }> export type TProviderManager = Readonly<{ remove: TRunnable & Pick & { target: TWithProviderID | undefined } }> export type TCheckProviderUpdateResult = Readonly<{ updateAvailable: boolean latestVersion?: string }> //#endregion //#region Workspace export type TWorkspaceID = NonNullable export type TWithWorkspaceID = Readonly<{ workspaceID: TWorkspaceID }> export type TWorkspace = Readonly<{ id: string uid: string picture: TMaybe machine: TMaybe }>> provider: TMaybe; options: TMaybe }>> status: TMaybe<"Running" | "Busy" | "Stopped" | "NotFound"> ide: TMaybe<{ name: TMaybe }> creationTimestamp: string lastUsed: string source: TMaybe }> export type TWorkspaceWithoutStatus = Omit & Readonly<{ status: null }> export type TWorkspaceStatusResult = Readonly<{ id: TMaybe context: TMaybe provider: TMaybe state: TMaybe }> export type TWorkspaceSourceType = "local" | "git" | "image" export type TWorkspaceStartConfig = Readonly<{ id: string prebuildRepositories?: string[] devcontainerPath?: string ideConfig?: TWorkspace["ide"] providerConfig?: Readonly<{ providerID?: TProviderID; options?: Record }> // Instead of starting a workspace just by ID, the sourceConfig starts it with a `source/ID` combination sourceConfig?: Readonly<{ source: string type?: TWorkspaceSourceType }> }> export type TWorkspaceSource = { gitRepository: TMaybe gitBranch: TMaybe gitCommit: TMaybe gitPRReference: TMaybe gitSubPath: TMaybe localFolder: TMaybe image: TMaybe } export const SUPPORTED_IDES = [ "none", "vscode", "vscode-insiders", "intellj", "goland", "rustrover", "pycharm", "phpstorm", "clion", "rubymine", "rider", "webstorm", "openvscode", "jupyternotebook", "fleet", "windsurf", ] as const export type TSupportedIDE = (typeof SUPPORTED_IDES)[number] export type TImportWorkspaceConfig = Readonly<{ workspaceID: string workspaceUID: string devPodProHost: string project: string options: Record | null }> //#endregion //#region Context export type TContextOptions = Record // See pkg/config/context.go export type TContextOptionName = | "AGENT_URL" | "TELEMETRY" | "SSH_INJECT_DOCKER_CREDENTIALS" | "SSH_INJECT_GIT_CREDENTIALS" export type TContextOption = Readonly<{ name: TContextOptionName description: string | null | undefined default: string | null | undefined enum: readonly string[] | null | undefined value: string | null | undefined }> //#endregion //#region Pro export type TProID = string export type TWithProID = Readonly<{ id: TProID }> export type TProInstance = Readonly<{ host: TMaybe provider: TMaybe creationTimestamp: TMaybe authenticated: TMaybe capabilities: TMaybe }> export type TProInstances = readonly TProInstance[] export type TProInstanceManager = Readonly<{ login: TRunnable & Pick, "status" | "error" | "reset"> & { provider: TProvider | undefined } disconnect: TRunnable & Pick & { target: TWithProID | undefined } }> export type TProInstanceLoginConfig = Readonly<{ host: string accessKey?: string streamListener?: TStreamEventListenerFn }> export type TListProInstancesConfig = Readonly< | { authenticate?: boolean } | undefined > export type TPlatformVersionInfo = Readonly<{ serverVersion: TMaybe remoteProviderVersion: TMaybe currentProviderVersion: TMaybe }> //#endregion export type TDevcontainerSetup = Readonly<{ isGitRepository: boolean isLocal: boolean isImage: boolean configPaths: string[] }> //#region CommunityContributions export type TCommunityContributions = Readonly<{ providers: readonly TCommunityProvider[] }> export type TCommunityProvider = Readonly<{ repository: string }> //#endregion export type TPlatformHealthCheck = Readonly<{ healthy: TMaybe online: TMaybe loginRequired: TMaybe details: TMaybe }> export type TPlatformUpdateCheck = Readonly<{ available: TMaybe newVersion: TMaybe }> export const UserSecret = { GIT_HTTP: "devpod-git-http", GIT_SSH: "devpod-git-ssh", } as const export type TUserSecretType = (typeof UserSecret)[keyof typeof UserSecret] export type TGitCredentialData = { password?: string key?: string host?: string user?: string path?: string } export type TGitCredentialHelperData = Readonly<{ host: string path?: string username?: string password: string }> export function isWithWorkspaceID(arg: unknown): arg is TWithWorkspaceID { return typeof arg === "object" && arg !== null && "workspaceID" in arg } ================================================ FILE: desktop/src/useCommunityContributions.ts ================================================ import { useQuery } from "@tanstack/react-query" import { client } from "./client" import { TCommunityContributions } from "./types" import { QueryKeys } from "./queryKeys" export function useCommunityContributions(): Readonly<{ contributions: TCommunityContributions | undefined isLoading: boolean }> { const { data, isLoading } = useQuery({ queryKey: QueryKeys.COMMUNITY_CONTRIBUTIONS, queryFn: async () => { return (await client.fetchCommunityContributions()).unwrap() }, refetchOnWindowFocus: false, refetchOnMount: false, refetchOnReconnect: false, }) return { contributions: data, isLoading } } ================================================ FILE: desktop/src/useIDEs.ts ================================================ import { useQuery } from "@tanstack/react-query" import { useMemo } from "react" import { client } from "./client" import { useSettings } from "./contexts" import { QueryKeys } from "./queryKeys" import { TIDE, TIDEs } from "@/types" // See pkg/config/ide.go for names const FLEET_IDE_NAME = "fleet" const JUPYTER_IDE_NAME = "jupyternotebook" const VSCODE_INSIDERS = "vscode-insiders" const CURSOR = "cursor" const POSITRON = "positron" const CODIUM = "codium" const ZED = "zed" const RSTUDIO = "rstudio" const WINDSURF = "windsurf" export function useIDEs() { const idesQuery = useQuery({ queryKey: QueryKeys.IDES, queryFn: async () => (await client.ides.listAll()).unwrap(), }) const settings = useSettings() const ides = useMemo( () => idesQuery.data?.filter((ide) => { if (!ide.experimental) return true if (ide.name === FLEET_IDE_NAME && settings.experimental_fleet) return true if (ide.name === JUPYTER_IDE_NAME && settings.experimental_jupyterNotebooks) return true if (ide.name === VSCODE_INSIDERS && settings.experimental_vscodeInsiders) return true if (ide.name === CURSOR && settings.experimental_cursor) return true if (ide.name === POSITRON && settings.experimental_positron) return true if (ide.name === CODIUM && settings.experimental_codium) return true if (ide.name === ZED && settings.experimental_zed) return true if (ide.name === RSTUDIO && settings.experimental_rstudio) return true if (ide.name === WINDSURF && settings.experimental_windsurf) return true return false }), [settings, idesQuery.data] ) return useMemo( () => ({ ides, defaultIDE: idesQuery.data?.find((ide) => ide.default) }), [ides, idesQuery.data] ) } export function useGroupIDEs(ides?: TIDEs) { return useMemo(() => { return ides?.reduce( (accum, ide) => { const group = ide.group ?? "Other" if (group === "Primary") { accum.primary.push(ide) } else { if (!accum.subMenus[group]) { accum.subMenus[group] = [] accum.subMenuGroups.push(group) } accum.subMenus[group]!.push(ide) } return accum }, { primary: [] as TIDE[], subMenuGroups: [] as string[], subMenus: {} as { [key: string]: TIDE[] }, } ) }, [ides]) } ================================================ FILE: desktop/src/useWelcomeModal.tsx ================================================ import { Code, Heading, HStack, Link, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalOverlay, Text, useDisclosure, VStack, } from "@chakra-ui/react" import { useCallback, useEffect, useMemo, useState } from "react" import { useNavigate } from "react-router" import { client } from "./client" import { LoftOSSBadge, Step, Steps, useInstallCLI } from "./components" import { Briefcase, CommandLine, DevpodWordmark } from "./icons" import { Routes } from "./routes" const IS_FIRST_VISIT_KEY = "devpod-is-first-visit" export function useWelcomeModal() { const navigate = useNavigate() const { isOpen, onClose, onOpen } = useDisclosure() const [isCancellable, setIsCancellable] = useState(false) const { badge: installCLIBadge, button: installCLIButton, helpText: installCLIHelpText, errorMessage: installCLIErrorMessage, } = useInstallCLI() const handleSetupFinished = useCallback(() => { onClose() navigate(Routes.WORKSPACE_CREATE) }, [navigate, onClose]) // Open the welcome modal on first visit, except if we start with a `SetupPro` event useEffect(() => { const maybeFirstVisit = localStorage.getItem(IS_FIRST_VISIT_KEY) let shouldShowWelcomeModal = maybeFirstVisit === null && !isOpen const listenerPromise = client.subscribe("event", (event) => { if (event.type === "SetupPro") { shouldShowWelcomeModal = false onClose() } }) if (shouldShowWelcomeModal) { onOpen() localStorage.setItem(IS_FIRST_VISIT_KEY, "false") return } return () => { listenerPromise.then((unsubscribe) => unsubscribe()) } }, [isOpen, onClose, onOpen]) const modal = useMemo(() => { return ( {isCancellable && } Welcome to DevPod is a tool to create reproducible developer environments. Each developer environment runs in a separate container and is specified through a devcontainer.json. Through DevPod providers these containers can be created on the local computer, any reachable remote machine or in a public or private cloud. It's also possible to extend DevPod and write your own custom providers.
For more information, head over to our{" "} client.open("https://devpod.sh/docs")}> documentation.
Let's set you up!
CLI DevPod ships with a powerful CLI that allows you to create, manage and connect to your workspaces and providers. You can either{" "} client.open("https://github.com/loft-sh/devpod/releases")}> download the standalone binary {" "} or directly add it to your $PATH.
Feel free to skip this step. You can always install the CLI from the{" "} Settings tab.
{installCLIButton} {installCLIBadge} {installCLIHelpText} {installCLIErrorMessage}
Workspaces Workspaces are your reproducible development environment on a per-project basis. Turn a local folder, git repository or docker container into a workspace and connect it to your favorite coding tool. Or just ssh into them start developing.
) }, [ handleSetupFinished, installCLIBadge, installCLIButton, installCLIErrorMessage, installCLIHelpText, isCancellable, isOpen, onClose, ]) const show = useCallback( ({ cancellable = false }: Readonly<{ cancellable?: boolean }>) => { setIsCancellable(cancellable) onOpen() }, [onOpen] ) return { modal, show } } ================================================ FILE: desktop/src/views/Actions/Action.tsx ================================================ import { useDownloadLogs } from "@/lib" import { DownloadIcon } from "@chakra-ui/icons" import { Box, Button, IconButton, Tooltip } from "@chakra-ui/react" import { useEffect, useMemo, useState } from "react" import { HiStop } from "react-icons/hi2" import { useNavigate } from "react-router" import { useParams, useSearchParams } from "react-router-dom" import { TerminalSearchBar, ToolbarActions, useStreamingTerminal } from "@/components" import { useAction } from "@/contexts" import { Routes } from "@/routes" import { TSearchOptions } from "@/components/Terminal/useTerminalSearch" export function Action() { const [searchParams] = useSearchParams() const params = useParams() const navigate = useNavigate() const actionID = useMemo(() => Routes.getActionID(params), [params]) const action = useAction(actionID) const [searchOptions, setSearchOptions] = useState({}) const { terminal, connectStream, clear, search: { totalSearchResults, nextSearchResult, prevSearchResult, activeSearchResult }, } = useStreamingTerminal({ searchOptions }) const { download, isDownloading: isDownloadingLogs } = useDownloadLogs() useEffect(() => { if (action === undefined) { return } clear() return action.connectOrReplay(connectStream) }, [action, actionID, clear, connectStream]) useEffect(() => { const onSuccess = searchParams.get("onSuccess") if (onSuccess && action?.data.status === "success") { navigate(onSuccess) } }, [searchParams, action, navigate]) return ( <> {action?.data.status === "pending" && ( )} {actionID !== undefined && ( } onClick={() => download({ actionID })} /> )} {terminal} ) } ================================================ FILE: desktop/src/views/Actions/Actions.tsx ================================================ import { Box } from "@chakra-ui/react" import { Outlet } from "react-router-dom" import { NavigationViewLayout } from "../../components" import { useActionTitle } from "./useActionTitle" export function Actions() { const title = useActionTitle() return ( ) } ================================================ FILE: desktop/src/views/Actions/index.ts ================================================ export { Action } from "./Action" export { Actions } from "./Actions" ================================================ FILE: desktop/src/views/Actions/useActionTitle.tsx ================================================ import { IconButton } from "@chakra-ui/react" import { useMemo } from "react" import { useLocation, useMatch, useNavigate } from "react-router-dom" import { TViewTitle } from "../../components" import { getAction, useWorkspaceStore } from "../../contexts" import { ArrowLeft } from "../../icons" import { exists, getActionDisplayName } from "../../lib" import { Routes } from "../../routes" export function useActionTitle(): TViewTitle | null { const { store } = useWorkspaceStore() const navigate = useNavigate() const location = useLocation() const matchAction = useMatch(Routes.ACTION) return useMemo(() => { if (!exists(matchAction)) { return null } const maybeActionID = Routes.getActionID(matchAction.params) if (!maybeActionID) { return null } const maybeAction = getAction(maybeActionID, store) if (maybeAction === undefined) { return null } const targetRoute = // Unfortunately `Location` isn't typed, so be careful if you change this exists(location.state?.origin) && location.state?.origin !== "" ? location.state?.origin : Routes.WORKSPACES return { label: getActionDisplayName(maybeAction), priority: "regular", leadingAction: ( } onClick={() => { navigate(targetRoute) }} /> ), } }, [location.state?.origin, matchAction, navigate, store]) } ================================================ FILE: desktop/src/views/Pro/BackToWorkspaces.tsx ================================================ import { useProContext } from "@/contexts" import { Routes } from "@/routes" import { ChevronLeftIcon } from "@chakra-ui/icons" import { Link } from "@chakra-ui/react" import { Link as RouterLink } from "react-router-dom" export function BackToWorkspaces() { const { host } = useProContext() return ( Back to Workspaces ) } ================================================ FILE: desktop/src/views/Pro/CreateWorkspace/CreateWorkspace.tsx ================================================ import { client as globalClient } from "@/client" import { ProWorkspaceInstance, ProWorkspaceStore, useProContext, useTemplates, useWorkspace, useWorkspaceStore, } from "@/contexts" import { Annotations, Failed, Labels, randomString, Result, Return, safeMaxName, Source, } from "@/lib" import { Routes } from "@/routes" import { Box, Heading, HStack, VStack } from "@chakra-ui/react" import { getProjectNamespace, NewResource, Resources } from "@loft-enterprise/client" import { ManagementV1DevPodWorkspaceInstance } from "@loft-enterprise/client/gen/models/managementV1DevPodWorkspaceInstance" import * as jsyaml from "js-yaml" import { useEffect, useMemo, useState } from "react" import { useNavigate } from "react-router-dom" import { BackToWorkspaces } from "../BackToWorkspaces" import { CreateWorkspaceForm } from "./CreateWorkspaceForm" import { TFormValues } from "./types" import { useLocation } from "react-router" import { DaemonClient } from "@/client/pro/client" export function CreateWorkspace() { const workspace = useWorkspace(undefined) const { store } = useWorkspaceStore() const [globalError, setGlobalError] = useState(null) const { host, currentProject, managementSelfQuery, client } = useProContext() const navigate = useNavigate() const { data: templates, isLoading: isTemplatesLoading } = useTemplates() const presets = templates?.presets const [presetId, setPresetId] = useState(undefined) const routerLocation = useLocation() useEffect(() => { const searchParams = new URLSearchParams(routerLocation.search) const fromPreset = searchParams.get("fromPreset") if (fromPreset && !presetId) { setPresetId(fromPreset) } // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const preset = useMemo(() => { if (!presetId) { return undefined } return (presets ?? []).find((p) => p.metadata?.name === presetId) }, [presetId, presets]) const handleReset = () => { setGlobalError(null) navigate(Routes.toProInstance(host)) } const handleSubmit = async (values: TFormValues) => { setGlobalError(null) const shouldUseRunner = !(client instanceof DaemonClient) const instanceRes = await buildWorkspaceInstance( values, currentProject?.metadata?.name, managementSelfQuery.data?.status?.projectNamespacePrefix, presetId, shouldUseRunner ) if (instanceRes.err) { setGlobalError(instanceRes.val) return } const createRes = await client.createWorkspace(instanceRes.val.instance) if (createRes.err) { setGlobalError(createRes.val) return } // update workspace store immediately const instance = new ProWorkspaceInstance(createRes.val) store.setWorkspace(instance.id, instance) workspace.create({ id: instanceRes.val.workspaceID, workspaceKey: instance.id, ideConfig: { name: values.defaultIDE, }, }) navigate(Routes.toProWorkspace(host, instance.id)) } return ( Create Workspace ) } async function buildWorkspaceInstance( values: TFormValues, currentProject: string | undefined, projectNamespacePrefix: string | undefined, preset: string | undefined, shouldUseRunner: boolean ): Promise> { const instance = NewResource(Resources.ManagementV1DevPodWorkspaceInstance) const workspaceSource = new Source(values.sourceType, values.source) // Workspace name const sourceIDRes = await globalClient.workspaces.newID(workspaceSource.stringify()) if (sourceIDRes.err) { return sourceIDRes } const id = getID(sourceIDRes.val) // Kubernetes name const kubeNameRes = await getKubeName(values.name || id) if (kubeNameRes.err) { return kubeNameRes } const kubeName = kubeNameRes.val // ID/UID const uidRes = await globalClient.workspaces.newUID() if (uidRes.err) { return uidRes } const uid = uidRes.val const displayName = values.name const ns = getProjectNamespace(currentProject, projectNamespacePrefix) if (!instance.metadata) { instance.metadata = {} } if (!instance.metadata.labels) { instance.metadata.labels = {} } if (!instance.metadata.annotations) { instance.metadata.annotations = {} } if (!instance.spec) { instance.spec = {} } instance.metadata.generateName = `${kubeName}-` instance.metadata.namespace = ns instance.metadata.labels[Labels.WorkspaceID] = id instance.metadata.labels[Labels.WorkspaceUID] = uid instance.metadata.annotations[Annotations.WorkspaceSource] = workspaceSource.stringify() instance.spec.displayName = displayName // TODO: Remove when removing proxy provider if (shouldUseRunner) { instance.spec.runnerRef = { runner: values.target, } } else { instance.spec.target = { cluster: { name: values.target }, } } // Template, version and parameters const { workspaceTemplate: template, workspaceTemplateVersion, ...parameters } = values.options try { instance.spec.parameters = jsyaml.dump(parameters) } catch (err) { return Return.Failed(err as any) } if (preset) { instance.spec.presetRef = { name: preset } } else { let templateVersion = workspaceTemplateVersion if (templateVersion === "latest") { templateVersion = "" } instance.spec.templateRef = { name: template, version: templateVersion, } // Environment template if (values.devcontainerType === "external") { instance.spec.environmentRef = { name: values.devcontainerJSON, } if (values.envTemplateVersion !== "latest") { instance.spec.environmentRef.version = values.envTemplateVersion } } } return Return.Value({ workspaceID: id, instance }) } async function getKubeName(name: string): Promise> { try { const kubeName = await safeMaxName( name .toLowerCase() .replace(/[^a-z0-9]/g, "-") .replace(/--+/g, "-") .replace(/(^[^a-z0-9])|([^a-z0-9]$)/, ""), 39 ) return Return.Value(kubeName) } catch (err) { return Return.Failed(`Failed to get kubernetes name from ${name}: ${err}`) } } function getID(name: string): string { if (name.length <= 48 - 6) { return `${name}-${randomString(5)}` } const start = name.substring(0, 48 - 6) return `${start}-${randomString(5)}` } ================================================ FILE: desktop/src/views/Pro/CreateWorkspace/CreateWorkspaceForm.tsx ================================================ import { BottomActionBar, BottomActionBarError, Form } from "@/components" import { ProWorkspaceInstance, useProjectClusters, useTemplates } from "@/contexts" import { Code, Laptop, Parameters } from "@/icons" import { Annotations, exists, Failed, getParametersWithValues, Labels, Source, useFormErrors, } from "@/lib" import { useIDEs } from "@/useIDEs" import { Box, Button, ButtonGroup, FormControl, FormErrorMessage, FormHelperText, FormLabel, Grid, Input, Spinner, VStack, useColorModeValue, } from "@chakra-ui/react" import { ManagementV1DevPodWorkspaceTemplate } from "@loft-enterprise/client/gen/models/managementV1DevPodWorkspaceTemplate" import { ReactNode, useCallback, useEffect, useMemo, useRef } from "react" import { Controller, DefaultValues, FormProvider, useForm } from "react-hook-form" import { DevContainerInput } from "./DevContainerInput" import { IDEInput } from "./IDEInput" import { InfrastructureTemplateInput } from "./InfrastructureTemplateInput" import { SourceInput } from "./SourceInput" import { FieldName, TFormValues } from "./types" import { TargetInput } from "@/views/Pro/CreateWorkspace/RunnerInput" import { ManagementV1DevPodWorkspacePreset } from "@loft-enterprise/client/gen/models/managementV1DevPodWorkspacePreset" import { Gold } from "@/icons/Gold" import { PresetInput } from "@/views/Pro/CreateWorkspace/PresetInput" type TCreateWorkspaceFormProps = Readonly<{ instance?: ProWorkspaceInstance template?: ManagementV1DevPodWorkspaceTemplate onSubmit: (data: TFormValues) => void onReset: VoidFunction error: Failed | null preset?: ManagementV1DevPodWorkspacePreset presets?: readonly ManagementV1DevPodWorkspacePreset[] setPreset?: (preset: string | undefined) => void loading?: boolean }> export function CreateWorkspaceForm({ instance, template, onSubmit, onReset, error, preset, presets, loading, setPreset, }: TCreateWorkspaceFormProps) { const defaultValues = useMemo(() => getDefaultValues(instance, template), [instance, template]) const containerRef = useRef(null) const { ides, defaultIDE } = useIDEs() const { data: templates, isLoading: isTemplatesLoading } = useTemplates() const { data: projectClusterData, isLoading: projectClusterDataLoading } = useProjectClusters() const form = useForm({ mode: "onChange", defaultValues }) const { sourceError, defaultIDEError, nameError, devcontainerJSONError, optionsError, targetError, } = useFormErrors(Object.values(FieldName), form.formState) const isUpdate = useMemo(() => { return !!instance }, [instance]) const resetPreset = useCallback(() => { setPreset?.(undefined) }, [setPreset]) useEffect(() => { if (!form.getFieldState(FieldName.DEFAULT_IDE).isDirty && defaultIDE && defaultIDE.name) { form.setValue(FieldName.DEFAULT_IDE, defaultIDE.name, { shouldDirty: false, shouldTouch: true, }) } }, [defaultIDE, form]) useEffect(() => { if (!isUpdate && preset) { const opts = { shouldDirty: true } // To enable the create workspace button. const sourceType = preset.spec?.source.image ? "image" : "git" const source = preset.spec?.source.image ?? preset.spec?.source.git form.setValue(FieldName.SOURCE_TYPE, sourceType, opts) form.setValue(FieldName.SOURCE, source ?? "", opts) if (preset.spec?.infrastructureRef.name) { form.setValue( `${FieldName.OPTIONS}.workspaceTemplate`, preset.spec.infrastructureRef.name, opts ) form.setValue( `${FieldName.OPTIONS}.workspaceTemplateVersion`, preset.spec.infrastructureRef.version ?? "latest", opts ) } if (preset.spec?.environmentRef?.name) { form.setValue(FieldName.DEVCONTAINER_TYPE, "external", opts) form.setValue(FieldName.DEVCONTAINER_JSON, preset.spec.environmentRef.name, opts) form.setValue( FieldName.ENV_TEMPLATE_VERSION, preset.spec.environmentRef.version ?? "latest", opts ) } else { form.setValue(FieldName.DEVCONTAINER_TYPE, "path", opts) form.setValue(FieldName.ENV_TEMPLATE_VERSION, "latest", opts) form.setValue(FieldName.DEVCONTAINER_JSON, "", opts) } } }, [preset, form, isUpdate]) const inputBg = useColorModeValue("white", "background.darkest") return (
{presets?.length ? ( Workspace Preset }> ) : ( <> )} Source Code }> {exists(sourceError) && ( {sourceError.message ?? "Error"} )} Parameters }> {isTemplatesLoading ? ( ) : ( )} {exists(optionsError) && ( {optionsError.message ?? "Error"} )} Cluster }> {projectClusterDataLoading ? ( ) : ( )} {exists(targetError) && ( {targetError.message ?? "Error"} )} Default IDE The default IDE to use when starting the workspace. This can be changed later. }> ( field.onChange(name)} /> )} /> {exists(defaultIDEError) && ( {defaultIDEError.message ?? "Error"} )} Devcontainer.json Set an external source or a relative path in the source code. Otherwise, we’ll look in the code repository. }> {exists(devcontainerJSONError) && ( {devcontainerJSONError.message ?? "Error"} )} Workspace Name }> {exists(nameError) && ( {nameError.message ?? "Error"} )} ) } type TCreateWorkspaceRowProps = Readonly<{ label: ReactNode children: ReactNode }> function CreateWorkspaceRow({ label, children }: TCreateWorkspaceRowProps) { return ( {label} {children} ) } function getDefaultValues( instance: ProWorkspaceInstance | undefined, template: ManagementV1DevPodWorkspaceTemplate | undefined ): DefaultValues | undefined { if (instance === undefined) { return undefined } const defaultValues: DefaultValues = { defaultIDE: instance.status?.ide?.name ?? "none", target: instance.spec?.runnerRef?.runner ?? instance.spec?.target?.cluster?.name, } // source const rawSource = instance.metadata?.annotations?.[Annotations.WorkspaceSource] if (rawSource) { const source = Source.fromRaw(rawSource) defaultValues.sourceType = source.type defaultValues.source = source.value } // infrastructure template if (template && instance.spec?.parameters) { if (!defaultValues.options) { defaultValues.options = {} } defaultValues.options.workspaceTemplate = instance.spec.templateRef?.name defaultValues.options.workspaceTemplateVersion = instance.spec.templateRef?.version const parameters = getParametersWithValues(instance, template) if (parameters && parameters.length > 0) { for (const parameter of parameters) { if (!parameter.variable) { continue } defaultValues.options[parameter.variable] = parameter.value } } } // environment template const environmentRefName = instance.spec?.environmentRef?.name if (environmentRefName) { defaultValues.devcontainerType = "external" defaultValues.devcontainerJSON = environmentRefName defaultValues.envTemplateVersion = instance.spec?.environmentRef?.version ?? "latest" } // name const name = instance.spec?.displayName ?? instance.metadata?.labels?.[Labels.WorkspaceID] if (name) { defaultValues.name = name } return defaultValues } ================================================ FILE: desktop/src/views/Pro/CreateWorkspace/DevContainerInput.tsx ================================================ import { getDisplayName } from "@/lib" import { Box, Input, InputGroup, InputLeftAddon, Select, useColorMode, useColorModeValue, useToken, } from "@chakra-ui/react" import { ManagementV1DevPodEnvironmentTemplate } from "@loft-enterprise/client/gen/models/managementV1DevPodEnvironmentTemplate" import { useEffect, useMemo, useState } from "react" import { useFormContext } from "react-hook-form" import { FieldName } from "./types" type TDevContainerInputProps = Readonly<{ environmentTemplates: readonly ManagementV1DevPodEnvironmentTemplate[] resetPreset?: VoidFunction }> export function DevContainerInput({ resetPreset, environmentTemplates: templates, }: TDevContainerInputProps) { const errorBorderColor = useToken("colors", "red.500") const { register, watch, resetField } = useFormContext() const devContainerType = watch(FieldName.DEVCONTAINER_TYPE, "path") const envTemplateValue = watch(FieldName.DEVCONTAINER_JSON) const bg = useColorModeValue("white", "background.darkest") const { colorMode } = useColorMode() const [envReference, setEnvReference] = useState(envTemplateValue) // Need the extra render cycle, because the form will not report the default value of the // environment template immediately if the type is changed. useEffect(() => { setEnvReference(envTemplateValue) }, [envTemplateValue]) const envTemplate = useMemo(() => { return determineEnvironmentTemplate(templates, envReference, devContainerType) }, [templates, envReference, devContainerType]) const versions = useMemo(() => { if (devContainerType === "path") { return undefined } return envTemplate?.spec?.versions?.map((v) => v.version!) }, [envTemplate, devContainerType]) const inputProps = useMemo( () => register(FieldName.DEVCONTAINER_JSON, { required: false, onChange: () => { resetPreset?.() resetField(FieldName.ENV_TEMPLATE_VERSION, { defaultValue: "latest" }) }, }), [register, resetField, resetPreset] ) const { input } = useMemo(() => { if (devContainerType === "path") { return { input: } } return { input: ( ), } }, [devContainerType, inputProps, templates]) return ( {input} {versions?.length && ( )} ) } function determineEnvironmentTemplate( templates: readonly ManagementV1DevPodEnvironmentTemplate[], envTemplateValue: string | undefined, devContainerType: "path" | "external" | undefined ): ManagementV1DevPodEnvironmentTemplate | undefined { if (devContainerType === "path" || !envTemplateValue) { return undefined } return templates.find((t) => t.metadata?.name === envTemplateValue) } ================================================ FILE: desktop/src/views/Pro/CreateWorkspace/IDEInput.tsx ================================================ import { IDEIcon } from "@/components" import { TIDE } from "@/types" import { InfoIcon } from "@chakra-ui/icons" import { Box, Card, HStack, Text, Tooltip, useColorModeValue } from "@chakra-ui/react" import { ReactElement, cloneElement } from "react" import { ControllerRenderProps } from "react-hook-form" import { FieldName, TFormValues } from "./types" type TIDEInputProps = Readonly<{ ides: readonly TIDE[] | undefined field: ControllerRenderProps onClick: (name: NonNullable) => void }> export function IDEInput({ ides, field, onClick }: TIDEInputProps) { return ( {ides?.map((ide) => { const isSelected = field.value === ide.name return ( } isSelected={isSelected} onClick={() => onClick(ide.name!)} /> ) })} ) } type TIDECardProps = Readonly<{ name: string icon: ReactElement isSelected: boolean onClick: VoidFunction }> function IDECard({ name, isSelected, icon, onClick }: TIDECardProps) { const iconColor = useColorModeValue("gray.700", "gray.500") let content = icon if (name === "None") { content = ( SSH ) } else { content = cloneElement(icon, { boxSize: "10" }) } return ( {content} ) } ================================================ FILE: desktop/src/views/Pro/CreateWorkspace/InfrastructureTemplateInput.tsx ================================================ import { useBorderColor } from "@/Theme" import { exists, getDisplayName, getParameters, sortByVersionDesc } from "@/lib" import { TProviderOption } from "@/types" import { TOptionWithID } from "@/views/Providers" import { FormControl, FormErrorMessage, FormHelperText, FormLabel, Input, Select, SimpleGrid, Switch, Textarea, VStack, useColorModeValue, } from "@chakra-ui/react" import { ManagementV1DevPodWorkspaceTemplate } from "@loft-enterprise/client/gen/models/managementV1DevPodWorkspaceTemplate" import { StorageV1AppParameter } from "@loft-enterprise/client/gen/models/storageV1AppParameter" import { ReactNode, useEffect, useMemo } from "react" import { ChangeHandler, Controller, useFormContext } from "react-hook-form" import { FieldName, TFormValues } from "./types" type TOptionsInputProps = Readonly<{ resetPreset?: VoidFunction infraTemplates: readonly ManagementV1DevPodWorkspaceTemplate[] defaultInfraTemplate: ManagementV1DevPodWorkspaceTemplate | undefined }> export function InfrastructureTemplateInput({ infraTemplates: templates, defaultInfraTemplate, resetPreset, }: TOptionsInputProps) { const { getValues, watch, resetField, formState, unregister, setValue } = useFormContext() const borderColor = useBorderColor() const defaultTemplate = defaultInfraTemplate ?? templates[0] const selectedTemplateName = watch( `${FieldName.OPTIONS}.workspaceTemplate`, defaultTemplate?.metadata?.name ) const selectedTemplateVersion = watch(`${FieldName.OPTIONS}.workspaceTemplateVersion`) const currentTemplate = useMemo( () => templates.find((template) => template.metadata?.name === selectedTemplateName), [selectedTemplateName, templates] ) const currentParameters = useMemo(() => { let v = selectedTemplateVersion if (selectedTemplateVersion === "latest") { v = "" } const params = getParameters(currentTemplate, v) return !params ? params : [...params] }, [currentTemplate, selectedTemplateVersion]) useEffect(() => { const value = getValues() // Apply default values manually when the set of parameters changes. currentParameters?.forEach((p) => { if (!p.variable) { return } const paramValue = getDeepValue(value.options, p.variable) if (paramValue == null && p.defaultValue != null) { if (p.type === "number") { setValue(`${FieldName.OPTIONS}.${p.variable}`, parseFloat(p.defaultValue)) } else if (p.type === "boolean") { setValue(`${FieldName.OPTIONS}.${p.variable}`, p.defaultValue === "true") } else { setValue(`${FieldName.OPTIONS}.${p.variable}`, p.defaultValue) } } }) // resetField won't properly delete the keys when you switch templates, // so it's probably best to clean it up every time the set of parameters changes... return () => { currentParameters?.forEach((p) => { unregister(`${FieldName.OPTIONS}.${p.variable?.split(/\./)[0]}`) }) } }, [currentParameters, unregister, getValues, setValue]) const currentTemplateVersions = useMemo(() => { return currentTemplate?.spec?.versions?.slice().sort(sortByVersionDesc) }, [currentTemplate?.spec?.versions]) const resetTemplate = () => { resetPreset?.() // reset all other options, including version const options = getValues("options") for (const [k] of Object.entries(options)) { if (k === "workspaceTemplate") { continue } if (k === "workspaceTemplateVersion") { resetField(`${FieldName.OPTIONS}.workspaceTemplateVersion`, { defaultValue: "latest", }) continue } resetField(`${FieldName.OPTIONS}.${k}`, {}) } } const resetTemplateVersion = () => { resetPreset?.() const resetOptions: Parameters[1] = { defaultValue: undefined, } // reset all parameters options const options = getValues("options") for (const [k] of Object.entries(options)) { if (k === "workspaceTemplate" || k === "workspaceTemplateVersion") { continue } resetField(`${FieldName.OPTIONS}.${k}`, resetOptions) } } const bg = useColorModeValue("gray.50", "gray.900") return ( ({ value: template.metadata!.name!, displayName: getDisplayName(template), }))} onChange={resetTemplate} /> {currentTemplateVersions && currentTemplateVersions.length > 0 && ( ({ value: version.version, displayName: version.version, })), ]} onChange={resetTemplateVersion} /> )} {currentParameters && currentParameters.length > 0 && ( {currentParameters.map((param) => { const paramID = param.variable if (!paramID) { return null } const fieldID = `${FieldName.OPTIONS}.${paramID}` let defaultValue = param.defaultValue if (typeof formState.defaultValues?.options?.[paramID] === "string") { defaultValue = formState.defaultValues.options[paramID] as string } return ( ({ value: option, displayName: option, }))} isRequired={param.required} /> ) })} )} ) } type TOptionFormFieldProps = Partial< Pick > & Readonly<{ id: string isRequired?: boolean placeholder?: string onChange?: VoidFunction }> function OptionFormField({ id, defaultValue, description, type, displayName, enum: enumProp, placeholder, isRequired = false, onChange, }: TOptionFormFieldProps) { const inputBackground = useColorModeValue("white", "background.darkest") const { register, formState, control } = useFormContext() const optionError = formState.errors[id] const input = useMemo(() => { const registerProps = register(id, { required: isRequired }) const defaultValueProp = exists(defaultValue) ? { defaultValue } : {} const props = { ...defaultValueProp, ...registerProps, onChange: (e: Parameters[0]) => { registerProps.onChange(e) onChange?.() }, background: inputBackground, } if (enumProp?.length) { let ph: string | undefined = placeholder ?? "Select option" if (defaultValue) { ph = undefined } return ( ) } switch (type) { case "boolean": return ( { let isChecked = value if (typeof value === "string") { isChecked = value === "true" } return ( onChange(e.target.checked)} onBlur={onBlur} isChecked={isChecked} /> ) }} /> ) case "number": return ( { // This prevents the input field from increasing/decreasing the numbers when the scroll event is captured // while this input field is focused ;(e.target as HTMLInputElement).blur() }} {...props} /> ) case "duration": return ( ) case "string": return ( ) case "multiline": return (