Setup for addition of dashboard self-hosted command (#417)

* added function to install dashboard binaries

* added to waitgroup

* updated installBinary method to support non-dapr repositories

* adding dashboard binaries

* binary install working for daprd and dashboard on linux

* added dashboard command for standalone

* updated dashboard documentation

* changed linux home directory to be outside of root

* fixing version issue

* moved location of extracted web folder

* fixing path issues on linux

* changed dashboard to take latest version

* updated documentation for dashboard standalone

* fixed issue where binary name was unknown in non-default path dapr install

* added user error message for dashboard not found error

* fixing linting issues

* adding dashboard compatibility changes

* mark dashboard flag as required

* syncing compatibility branch and master

* fixing uninstall error

* removing unnecessary check

* removing unused constants

* changed standalone untar method openfile mode

* added missing error message

* removed windows binary check in untar method

* updating unzip method to support multiple files in archive

* adding sanitizeExtractPath method and limiting archive copy bytes

* changed max file size to 100MB

* removing max file size limit

* removing debug statement

* removing tabs in cmd/dashboard.go

* fixing whitespace issues

Co-authored-by: Shalabh Mohan Shrivastava <shalabhs@microsoft.com>
This commit is contained in:
Will Smith 2020-08-04 13:48:45 -05:00 committed by GitHub
parent 06d64bf7ac
commit 12b0651c94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 159 additions and 133 deletions

View File

@ -49,90 +49,92 @@ var DashboardCmd = &cobra.Command{
localPort = port
}
config, client, err := kubernetes.GetKubeConfigClient()
if err != nil {
print.FailureStatusEvent(os.Stdout, "Failed to initialize kubernetes client: %s", err.Error())
os.Exit(1)
}
// search for dashboard service namespace in order:
// user-supplied namespace, dapr-system, default
namespaces := []string{dashboardNamespace}
if dashboardNamespace != daprSystemNamespace {
namespaces = append(namespaces, daprSystemNamespace)
}
if dashboardNamespace != defaultNamespace {
namespaces = append(namespaces, defaultNamespace)
}
foundNamespace := ""
for _, namespace := range namespaces {
ok, _ := kubernetes.CheckPodExists(client, namespace, nil, dashboardSvc)
if ok {
foundNamespace = namespace
break
if kubernetesMode {
config, client, err := kubernetes.GetKubeConfigClient()
if err != nil {
print.FailureStatusEvent(os.Stdout, "Failed to initialize kubernetes client: %s", err.Error())
os.Exit(1)
}
}
// if the service is not found, try to search all pods
if foundNamespace == "" {
ok, nspace := kubernetes.CheckPodExists(client, "", nil, dashboardSvc)
// if the service is found, tell the user to try with the found namespace
// if the service is still not found, throw an error
if ok {
print.InfoStatusEvent(os.Stdout, "Dapr dashboard found in namespace: %s. Run dapr dashboard -k -n %s to use this namespace.", nspace, nspace)
} else {
print.FailureStatusEvent(os.Stdout, "Failed to find Dapr dashboard in cluster. Check status of dapr dashboard in the cluster.")
// search for dashboard service namespace in order:
// user-supplied namespace, dapr-system, default
namespaces := []string{dashboardNamespace}
if dashboardNamespace != daprSystemNamespace {
namespaces = append(namespaces, daprSystemNamespace)
}
os.Exit(1)
if dashboardNamespace != defaultNamespace {
namespaces = append(namespaces, defaultNamespace)
}
foundNamespace := ""
for _, namespace := range namespaces {
ok, _ := kubernetes.CheckPodExists(client, namespace, nil, dashboardSvc)
if ok {
foundNamespace = namespace
break
}
}
// if the service is not found, try to search all pods
if foundNamespace == "" {
ok, nspace := kubernetes.CheckPodExists(client, "", nil, dashboardSvc)
// if the service is found, tell the user to try with the found namespace
// if the service is still not found, throw an error
if ok {
print.InfoStatusEvent(os.Stdout, "Dapr dashboard found in namespace: %s. Run dapr dashboard -k -n %s to use this namespace.", nspace, nspace)
} else {
print.FailureStatusEvent(os.Stdout, "Failed to find Dapr dashboard in cluster. Check status of dapr dashboard in the cluster.")
}
os.Exit(1)
}
// manage termination of port forwarding connection on interrupt
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt)
defer signal.Stop(signals)
portForward, err := kubernetes.NewPortForward(
config,
foundNamespace,
dashboardSvc,
defaultHost,
localPort,
remotePort,
false,
)
if err != nil {
print.FailureStatusEvent(os.Stdout, "%s\n", err)
os.Exit(1)
}
// initialize port forwarding
if err = portForward.Init(); err != nil {
print.FailureStatusEvent(os.Stdout, "Error in port forwarding: %s\nCheck for `dapr dashboard` running in other terminal sessions, or use the `--port` flag to use a different port.\n", err)
os.Exit(1)
}
// block until interrupt signal is received
go func() {
<-signals
portForward.Stop()
}()
// url for dashboard after port forwarding
var webURL string = fmt.Sprintf("http://%s:%d", defaultHost, localPort)
print.InfoStatusEvent(os.Stdout, fmt.Sprintf("Dapr dashboard found in namespace:\t%s", foundNamespace))
print.InfoStatusEvent(os.Stdout, fmt.Sprintf("Dapr dashboard available at:\t%s\n", webURL))
err = browser.OpenURL(webURL)
if err != nil {
print.FailureStatusEvent(os.Stdout, "Failed to start Dapr dashboard in browser automatically")
print.FailureStatusEvent(os.Stdout, fmt.Sprintf("Visit %s in your browser to view the dashboard", webURL))
}
<-portForward.GetStop()
}
// manage termination of port forwarding connection on interrupt
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt)
defer signal.Stop(signals)
portForward, err := kubernetes.NewPortForward(
config,
foundNamespace,
dashboardSvc,
defaultHost,
localPort,
remotePort,
false,
)
if err != nil {
print.FailureStatusEvent(os.Stdout, "%s\n", err)
os.Exit(1)
}
// initialize port forwarding
if err = portForward.Init(); err != nil {
print.FailureStatusEvent(os.Stdout, "Error in port forwarding: %s\nCheck for `dapr dashboard` running in other terminal sessions, or use the `--port` flag to use a different port.\n", err)
os.Exit(1)
}
// block until interrupt signal is received
go func() {
<-signals
portForward.Stop()
}()
// url for dashboard after port forwarding
var webURL string = fmt.Sprintf("http://%s:%d", defaultHost, localPort)
print.InfoStatusEvent(os.Stdout, fmt.Sprintf("Dapr dashboard found in namespace:\t%s", foundNamespace))
print.InfoStatusEvent(os.Stdout, fmt.Sprintf("Dapr dashboard available at:\t%s\n", webURL))
err = browser.OpenURL(webURL)
if err != nil {
print.FailureStatusEvent(os.Stdout, "Failed to start Dapr dashboard in browser automatically")
print.FailureStatusEvent(os.Stdout, fmt.Sprintf("Visit %s in your browser to view the dashboard", webURL))
}
<-portForward.GetStop()
},
}

View File

@ -148,11 +148,11 @@ func Init(runtimeVersion string, dockerNetwork string, redisHost string, slimMod
}
// Initialize daprd binary
go installBinary(&wg, errorChan, daprBinDir, runtimeVersion, daprRuntimeFilePrefix, dockerNetwork)
go installBinary(&wg, errorChan, daprBinDir, runtimeVersion, daprRuntimeFilePrefix, dockerNetwork, cli_ver.DaprGitHubRepo)
if slimMode {
// Initialize placement binary only on slim install
go installBinary(&wg, errorChan, daprBinDir, runtimeVersion, placementServiceFilePrefix, dockerNetwork)
go installBinary(&wg, errorChan, daprBinDir, runtimeVersion, placementServiceFilePrefix, dockerNetwork, cli_ver.DaprGitHubRepo)
} else {
for _, step := range initSteps {
// Run init on the configurations and containers
@ -429,29 +429,29 @@ func runPlacementService(wg *sync.WaitGroup, errorChan chan<- error, dir, versio
errorChan <- nil
}
func installBinary(wg *sync.WaitGroup, errorChan chan<- error, dir, version, binaryFilePrefix string, dockerNetwork string) {
func installBinary(wg *sync.WaitGroup, errorChan chan<- error, dir, version, binaryFilePrefix string, dockerNetwork string, githubRepo string) {
defer wg.Done()
archiveExt := "tar.gz"
if runtime.GOOS == daprWindowsOS {
archiveExt = "zip"
}
if version == daprLatestVersion {
var err error
version, err = cli_ver.GetLatestRelease(cli_ver.DaprGitHubOrg, cli_ver.DaprGitHubRepo)
version, err = cli_ver.GetLatestRelease(cli_ver.DaprGitHubOrg, githubRepo)
if err != nil {
errorChan <- fmt.Errorf("cannot get the latest release version: %s", err)
return
}
version = version[1:]
}
// https://github.com/dapr/dapr/releases/download/v0.8.0/daprd_darwin_amd64.tar.gz
// https://github.com/dapr/dapr/releases/download/v0.8.0/placement_darwin_amd64.tar.gz
fileURL := fmt.Sprintf(
"https://github.com/%s/%s/releases/download/v%s/%s_%s_%s.%s",
cli_ver.DaprGitHubOrg,
cli_ver.DaprGitHubRepo,
githubRepo,
version,
binaryFilePrefix,
runtime.GOOS,
@ -467,7 +467,7 @@ func installBinary(wg *sync.WaitGroup, errorChan chan<- error, dir, version, bin
extractedFilePath := ""
if archiveExt == "zip" {
extractedFilePath, err = unzip(filepath, dir)
extractedFilePath, err = unzip(filepath, dir, binaryFilePrefix)
} else {
extractedFilePath, err = untar(filepath, dir, binaryFilePrefix)
}
@ -554,47 +554,63 @@ func makeExecutable(filepath string) error {
return nil
}
func unzip(filepath, targetDir string) (string, error) {
zipReader, err := zip.OpenReader(filepath)
// https://github.com/snyk/zip-slip-vulnerability, fixes gosec G305
func sanitizeExtractPath(destination string, filePath string) (string, error) {
destpath := path_filepath.Join(destination, filePath)
if !strings.HasPrefix(destpath, path_filepath.Clean(destination)+string(os.PathSeparator)) {
return "", fmt.Errorf("%s: illegal file path", filePath)
}
return destpath, nil
}
func unzip(filepath, targetDir, binaryFilePrefix string) (string, error) {
r, err := zip.OpenReader(filepath)
if err != nil {
return "", err
}
defer zipReader.Close()
defer r.Close()
if len(zipReader.Reader.File) > 0 {
file := zipReader.Reader.File[0]
zippedFile, err := file.Open()
foundBinary := ""
for _, f := range r.File {
fpath, err := sanitizeExtractPath(targetDir, f.Name)
if err != nil {
return "", err
}
defer zippedFile.Close()
extractedFilePath := path.Join(
targetDir,
file.Name,
)
if strings.HasSuffix(fpath, fmt.Sprintf("%s.exe", binaryFilePrefix)) {
foundBinary = fpath
}
outputFile, err := os.OpenFile(
extractedFilePath,
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
file.Mode(),
)
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm)
continue
}
if err = os.MkdirAll(path_filepath.Dir(fpath), os.ModePerm); err != nil {
return "", err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return "", err
}
rc, err := f.Open()
if err != nil {
return "", err
}
defer outputFile.Close()
// #nosec G110
_, err = io.Copy(outputFile, zippedFile)
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return "", err
}
return extractedFilePath, nil
}
return "", nil
return foundBinary, nil
}
func untar(filepath, targetDir, binaryFilePrefix string) (string, error) {
@ -612,41 +628,49 @@ func untar(filepath, targetDir, binaryFilePrefix string) (string, error) {
tr := tar.NewReader(gzr)
foundBinary := ""
for {
header, err := tr.Next()
switch {
case err == io.EOF:
return "", fmt.Errorf("file is empty")
case err != nil:
if err == io.EOF {
break
} else if err != nil {
return "", err
case header == nil:
} else if header == nil {
continue
}
extractedFilePath := path.Join(targetDir, header.Name)
// untar all files in archive
path, err := sanitizeExtractPath(targetDir, header.Name)
if err != nil {
return "", err
}
switch header.Typeflag {
case tar.TypeReg:
// Extract only the binaryFile
if header.Name != binaryFilePrefix {
continue
}
f, err := os.OpenFile(extractedFilePath, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
info := header.FileInfo()
if info.IsDir() {
if err = os.MkdirAll(path, info.Mode()); err != nil {
return "", err
}
continue
}
// #nosec G110
if _, err := io.Copy(f, tr); err != nil {
return "", err
}
f.Close()
f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return "", err
}
defer f.Close()
return extractedFilePath, nil
// #nosec G110
if _, err = io.Copy(f, tr); err != nil {
return "", err
}
// If the found file is the binary that we want to find, save it and return later
if strings.HasSuffix(header.Name, binaryFilePrefix) {
foundBinary = path
}
}
return foundBinary, nil
}
func moveFileToPath(filepath string, installLocation string) (string, error) {
@ -716,7 +740,7 @@ func downloadFile(dir string, url string) (string, error) {
defer resp.Body.Close()
if resp.StatusCode == 404 {
return "", errors.New("runtime version not found")
return "", fmt.Errorf("version not found from url: %s", url)
} else if resp.StatusCode != 200 {
return "", fmt.Errorf("download failed with %d", resp.StatusCode)
}