Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add local helm repo support #1366

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,15 @@ export DAPR_HELM_REPO_PASSWORD="passwd_xxx"

Setting the above parameters will allow `dapr init -k` to install Dapr images from the configured Helm repository.

A local Helm repo is also supported, this can either be a directory path or an existing .tgz file.

export DAPR_HELM_REPO_URL="/home/user/dapr/helm-charts"

To directly use HEAD helm charts, create two local symlinks under `DAPR_HELM_REPO_URL` that point to the `charts` folder of each repo:
* `dapr-dashboard-latest` -> `https://github.com/dapr/dashboard/tree/master/chart/dapr-dashboard`
* `dapr-latest` -> `https://github.com/dapr/dapr/tree/master/charts/dapr`


### Launch Dapr and your app

The Dapr CLI lets you debug easily by launching both Dapr and your app.
Expand Down
47 changes: 35 additions & 12 deletions pkg/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,12 +190,15 @@ func getVersion(releaseName string, version string) (string, error) {
return actualVersion, nil
}

func createTempDir() (string, error) {
func createTempDir() (string, func(), error) {
dir, err := os.MkdirTemp("", "dapr")
if err != nil {
return "", fmt.Errorf("error creating temp dir: %w", err)
return "", func() {}, fmt.Errorf("error creating temp dir: %w", err)
}
return dir, nil
cleanup := func() {
os.RemoveAll(dir)
}
return dir, cleanup, nil
}

func locateChartFile(dirPath string) (string, error) {
Expand All @@ -206,7 +209,12 @@ func locateChartFile(dirPath string) (string, error) {
return filepath.Join(dirPath, files[0].Name()), nil
}

func getHelmChart(version, releaseName, helmRepo string, config *helm.Configuration) (*chart.Chart, error) {
func pullHelmChart(version, releaseName, helmRepo string, config *helm.Configuration) (string, func(), error) {
// is helmRepo already a directory path or a .tgz file? (ie. /home/user/dapr/helm-charts).
if localPath, err := utils.DiscoverHelmPath(helmRepo, releaseName, version); err == nil {
return localPath, func() {}, nil
}

pull := helm.NewPullWithOpts(helm.WithConfig(config))
pull.RepoURL = helmRepo
pull.Username = utils.GetEnv("DAPR_HELM_REPO_USERNAME", "")
Expand All @@ -218,24 +226,39 @@ func getHelmChart(version, releaseName, helmRepo string, config *helm.Configurat
pull.Version = chartVersion(version)
}

dir, err := createTempDir()
dir, cleanup, err := createTempDir()
if err != nil {
return nil, err
return "", nil, fmt.Errorf("unable to create temp dir: %w", err)
}
defer os.RemoveAll(dir)

pull.DestDir = dir

_, err = pull.Run(releaseName)
if err != nil {
return nil, err
return "", cleanup, fmt.Errorf("unable to pull chart from repo: %w", err)
}

chartPath, err := locateChartFile(dir)
if err != nil {
return nil, err
return "", cleanup, fmt.Errorf("unable to locate chart: %w", err)
}
return loader.Load(chartPath)

return chartPath, cleanup, nil
}

func getHelmChart(version, releaseName, helmRepo string, config *helm.Configuration) (*chart.Chart, error) {
chartPath, cleanup, err := pullHelmChart(version, releaseName, helmRepo, config)
defer cleanup()
if err != nil {
return nil, fmt.Errorf("unable to pull helm chart: %w", err)
}

chart, err := loader.Load(chartPath)
if err != nil {
return nil, fmt.Errorf("unable to load chart from path: %w", err)
}

return chart, nil
}

func daprChartValues(config InitConfiguration, version string) (map[string]interface{}, error) {
Expand Down Expand Up @@ -461,8 +484,8 @@ spec:
zipkin:
endpointAddress: "http://dapr-dev-zipkin.default.svc.cluster.local:9411/api/v2/spans"
`
tempDirPath, err := createTempDir()
defer os.RemoveAll(tempDirPath)
tempDirPath, cleanup, err := createTempDir()
defer cleanup()
if err != nil {
return err
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/runfileconfig/run_file_config_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func (a *RunFileConfig) resolvePathToAbsAndValidate(baseDir string, paths ...*st
return err
}
*path = absPath
if err = utils.ValidateFilePath(*path); err != nil {
if err = utils.ValidatePath(*path); err != nil {
return err
}
}
Expand Down Expand Up @@ -264,7 +264,7 @@ func (a *RunFileConfig) resolveResourcesFilePath(app *App) error {
return nil
}
localResourcesDir := filepath.Join(app.AppDirPath, standalone.DefaultDaprDirName, standalone.DefaultResourcesDirName)
if err := utils.ValidateFilePath(localResourcesDir); err == nil {
if err := utils.ValidatePath(localResourcesDir); err == nil {
app.ResourcesPaths = []string{localResourcesDir}
} else if len(a.Common.ResourcesPaths) > 0 {
app.ResourcesPaths = append(app.ResourcesPaths, a.Common.ResourcesPaths...)
Expand All @@ -285,7 +285,7 @@ func (a *RunFileConfig) resolveConfigFilePath(app *App) error {
return nil
}
localConfigFile := filepath.Join(app.AppDirPath, standalone.DefaultDaprDirName, standalone.DefaultConfigFileName)
if err := utils.ValidateFilePath(localConfigFile); err == nil {
if err := utils.ValidatePath(localConfigFile); err == nil {
app.ConfigFile = localConfigFile
} else if len(strings.TrimSpace(a.Common.ConfigFile)) > 0 {
app.ConfigFile = a.Common.ConfigFile
Expand Down
6 changes: 4 additions & 2 deletions tests/e2e/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -774,8 +774,10 @@ func installTest(details VersionDetails, opts TestOptions) func(t *testing.T) {
args = append(args, "--dev")
}
if !details.UseDaprLatestVersion {
// TODO: Pass dashboard-version also when charts are released.
args = append(args, "--runtime-version", details.RuntimeVersion)
args = append(args, []string{
"--runtime-version", details.RuntimeVersion,
"--dashboard-version", details.DashboardVersion,
}...)
}
if opts.HAEnabled {
args = append(args, "--enable-ha")
Expand Down
131 changes: 131 additions & 0 deletions tests/e2e/kubernetes/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ limitations under the License.
package kubernetes_test

import (
"archive/tar"
"compress/gzip"
"io"
"os"
"path/filepath"
"strings"
"testing"

"github.com/dapr/cli/tests/e2e/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestKubernetesNonHAModeMTLSDisabled(t *testing.T) {
Expand Down Expand Up @@ -500,3 +508,126 @@ func TestK8sInstallwithoutRuntimeVersionwithMarinerImagesFlag(t *testing.T) {
t.Run(tc.Name, tc.Callable)
}
}

func TestKubernetesLocalFileHelmRepoInstall(t *testing.T) {
// ensure clean env for test
ensureCleanEnv(t, false)

// create a temp dir to store the helm repo
helmRepoPath, err := os.MkdirTemp("", "dapr-e2e-kube-with-env-*")
assert.NoError(t, err)
// defer os.RemoveAll(helmRepoPath) // clean up

// copy all .tar.gz files from testdata dir and uncompress them
copyAndUncompressTarGzFiles(t, helmRepoPath)

// point the env var to the dir containing both dapr and dapr-dashboard helm charts
t.Setenv("DAPR_HELM_REPO_URL", helmRepoPath)

// setup tests
tests := []common.TestCase{}
tests = append(tests, common.GetTestsOnInstall(currentVersionDetails, common.TestOptions{
HAEnabled: false,
MTLSEnabled: false,
ApplyComponentChanges: true,
CheckResourceExists: map[common.Resource]bool{
common.CustomResourceDefs: true,
common.ClusterRoles: true,
common.ClusterRoleBindings: true,
},
})...)

tests = append(tests, common.GetTestsOnUninstall(currentVersionDetails, common.TestOptions{
CheckResourceExists: map[common.Resource]bool{
common.CustomResourceDefs: true,
common.ClusterRoles: false,
common.ClusterRoleBindings: false,
},
})...)

// execute tests
for _, tc := range tests {
t.Run(tc.Name, tc.Callable)
}
}

func copyAndUncompressTarGzFiles(t *testing.T, destination string) {
// find all .tar.gz files in testdata dir
files, err := filepath.Glob(filepath.Join("testdata", "*.tgz"))
require.NoError(t, err)

for _, file := range files {
// untar the dapr/dashboard helm .tar.gz, get back the root dir of the untarred files
// it's either 'dapr' or 'dapr-dashboard'
rootDir, err := untarDaprHelmGzFile(file, destination)
require.NoError(t, err)

// rename the root dir to the base name of the .tar.gz file
// (eg. /var/folders/4s/w0gdrc957k11vbkgyhjrk12w0000gn/T/dapr-e2e-kube-with-env-404115459/dapr-1.12.0)
base := filepath.Base(strings.TrimSuffix(file, filepath.Ext(file)))
err = os.Rename(filepath.Join(destination, rootDir), filepath.Join(destination, base))
require.NoError(t, err)
}
}

func untarDaprHelmGzFile(file string, destination string) (string, error) {
// open the tar.gz file
f, err := os.Open(file)
if err != nil {
return "", err
}
defer f.Close()

// create a gzip reader
gr, err := gzip.NewReader(f)
if err != nil {
return "", err
}
defer gr.Close()

// create a tar reader
tr := tar.NewReader(gr)

rootDir := ""
// iterate through all the files in the tarball
for {
hdr, err := tr.Next()
if err == io.EOF {
break // end of tarball
}
if err != nil {
return "", err
}

// build the full destination path
filename := filepath.Join(destination, hdr.Name)

// ensure the destination directory exists
dir := filepath.Dir(filename)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err := os.MkdirAll(dir, 0700); err != nil {
return "", err
}
}

// the root dir for all files is the same
rootDir = strings.FieldsFunc(hdr.Name,
func(c rune) bool {
return os.PathSeparator == c
})[0]

// create the destination file
dstFile, err := os.Create(filename)
if err != nil {
return "", err
}
defer dstFile.Close()

// copy the file contents
if _, err := io.Copy(dstFile, tr); err != nil {
return "", err
}
}

return rootDir, nil
}
Binary file added tests/e2e/kubernetes/testdata/dapr-1.12.0.tgz
Binary file not shown.
Binary file not shown.
33 changes: 26 additions & 7 deletions utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,13 +351,16 @@ func GetVersionAndImageVariant(imageTag string) (string, string) {
return imageTag, ""
}

// Returns true if the given file path is valid.
func ValidateFilePath(filePath string) error {
if filePath != "" {
if _, err := os.Stat(filePath); err != nil {
return fmt.Errorf("error in getting the file info for %s: %w", filePath, err)
}
// Returns no error if the given file/directory path is valid.
func ValidatePath(path string) error {
if path == "" {
return nil
}

if _, err := os.Stat(path); err != nil {
return fmt.Errorf("error in getting the file info for %s: %w", path, err)
}

return nil
}

Expand Down Expand Up @@ -409,7 +412,7 @@ func ReadFile(filePath string) ([]byte, error) {
// FindFileInDir finds and returns the path of the given file name in the given directory.
func FindFileInDir(dirPath, fileName string) (string, error) {
filePath := filepath.Join(dirPath, fileName)
if err := ValidateFilePath(filePath); err != nil {
if err := ValidatePath(filePath); err != nil {
return "", fmt.Errorf("error in validating the file path %q: %w", filePath, err)
}
return filePath, nil
Expand All @@ -430,3 +433,19 @@ func AttachJobObjectToProcess(pid string, proc *os.Process) {
func GetJobObjectNameFromPID(pid string) string {
return pid + "-" + windowsDaprAppProcJobName
}

func DiscoverHelmPath(helmPath, release, version string) (string, error) {
// first try for a local directory path.
dirPath := filepath.Join(helmPath, fmt.Sprintf("%s-%s", release, version))
if ValidatePath(dirPath) == nil {
return dirPath, nil
}

// not a dir, try a .tgz file instead.
filePath := filepath.Join(helmPath, fmt.Sprintf("%s-%s.tgz", release, version))
if ValidatePath(filePath) == nil {
return filePath, nil
}

return "", fmt.Errorf("unable to find a helm path in either %s or %s", dirPath, filePath)
}
13 changes: 10 additions & 3 deletions utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,12 @@ func TestGetVersionAndImageVariant(t *testing.T) {
}
}

func TestValidateFilePaths(t *testing.T) {
func TestValidatePaths(t *testing.T) {
dirName := createTempDir(t, "test_validate_paths")
defer cleanupTempDir(t, dirName)
validFile := createTempFile(t, dirName, "valid_test_file.yaml")
t.Cleanup(func() {
cleanupTempDir(t, dirName)
})
testcases := []struct {
name string
input string
Expand All @@ -194,6 +196,11 @@ func TestValidateFilePaths(t *testing.T) {
input: validFile,
expectedErr: false,
},
{
name: "valid directory path",
input: dirName,
expectedErr: false,
},
{
name: "invalid file path",
input: "invalid_file_path",
Expand All @@ -205,7 +212,7 @@ func TestValidateFilePaths(t *testing.T) {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
actual := ValidateFilePath(tc.input)
actual := ValidatePath(tc.input)
assert.Equal(t, tc.expectedErr, actual != nil)
})
}
Expand Down
Loading