Skip to content

Commit

Permalink
Adds support for Helm and Kustomize for AKS service target (#3196)
Browse files Browse the repository at this point in the history
Adds Helm and Kustomize support for AKS service targets.
  • Loading branch information
wbreza committed Feb 9, 2024
1 parent 1cd55ed commit 153a496
Show file tree
Hide file tree
Showing 23 changed files with 1,575 additions and 86 deletions.
4 changes: 4 additions & 0 deletions cli/azd/.vscode/cspell.yaml
Expand Up @@ -88,6 +88,10 @@ overrides:
- filename: pkg/azsdk/storage/storage_blob_client.go
words:
- azblob
- filename: pkg/project/service_target_aks.go
words:
- kustomization
- templating
ignorePaths:
- "**/*_test.go"
- "**/mock*.go"
8 changes: 6 additions & 2 deletions cli/azd/cmd/container.go
Expand Up @@ -28,12 +28,14 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/environment"
"github.com/azure/azure-dev/cli/azd/pkg/environment/azdcontext"
"github.com/azure/azure-dev/cli/azd/pkg/exec"
"github.com/azure/azure-dev/cli/azd/pkg/helm"
"github.com/azure/azure-dev/cli/azd/pkg/httputil"
"github.com/azure/azure-dev/cli/azd/pkg/infra"
"github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning"
"github.com/azure/azure-dev/cli/azd/pkg/input"
"github.com/azure/azure-dev/cli/azd/pkg/ioc"
"github.com/azure/azure-dev/cli/azd/pkg/kubelogin"
"github.com/azure/azure-dev/cli/azd/pkg/kustomize"
"github.com/azure/azure-dev/cli/azd/pkg/lazy"
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/azure/azure-dev/cli/azd/pkg/pipeline"
Expand Down Expand Up @@ -466,6 +468,8 @@ func registerCommonDependencies(container *ioc.NestedContainer) {
container.MustRegisterSingleton(kubectl.NewKubectl)
container.MustRegisterSingleton(maven.NewMavenCli)
container.MustRegisterSingleton(kubelogin.NewCli)
container.MustRegisterSingleton(helm.NewCli)
container.MustRegisterSingleton(kustomize.NewCli)
container.MustRegisterSingleton(npm.NewNpmCli)
container.MustRegisterSingleton(python.NewPythonCli)
container.MustRegisterSingleton(swa.NewSwaCli)
Expand All @@ -481,7 +485,7 @@ func registerCommonDependencies(container *ioc.NestedContainer) {

// Service Targets
serviceTargetMap := map[project.ServiceTargetKind]any{
"": project.NewAppServiceTarget,
project.NonSpecifiedTarget: project.NewAppServiceTarget,
project.AppServiceTarget: project.NewAppServiceTarget,
project.AzureFunctionTarget: project.NewFunctionAppTarget,
project.ContainerAppTarget: project.NewContainerAppTarget,
Expand All @@ -497,7 +501,7 @@ func registerCommonDependencies(container *ioc.NestedContainer) {

// Languages
frameworkServiceMap := map[project.ServiceLanguageKind]any{
"": project.NewDotNetProject,
project.ServiceLanguageNone: project.NewNoOpProject,
project.ServiceLanguageDotNet: project.NewDotNetProject,
project.ServiceLanguageCsharp: project.NewDotNetProject,
project.ServiceLanguageFsharp: project.NewDotNetProject,
Expand Down
167 changes: 167 additions & 0 deletions cli/azd/pkg/helm/cli.go
@@ -0,0 +1,167 @@
package helm

import (
"context"
"encoding/json"
"fmt"
"log"
"time"

"github.com/azure/azure-dev/cli/azd/pkg/exec"
"github.com/azure/azure-dev/cli/azd/pkg/tools"
)

type Cli struct {
commandRunner exec.CommandRunner
}

func NewCli(commandRunner exec.CommandRunner) *Cli {
return &Cli{
commandRunner: commandRunner,
}
}

// Gets the name of the Tool
func (cli *Cli) Name() string {
return "helm"
}

// Returns the installation URL to install the Helm CLI
func (cli *Cli) InstallUrl() string {
return "https://aka.ms/azure-dev/helm-install"
}

// Checks whether or not the Helm CLI is installed and available within the PATH
func (cli *Cli) CheckInstalled(ctx context.Context) error {
if err := tools.ToolInPath("helm"); err != nil {
return err
}

// We don't have a minimum required version of helm today, but
// for diagnostics purposes, let's fetch and log the version of helm
// we're using.
if ver, err := cli.getClientVersion(ctx); err != nil {
log.Printf("error fetching helm version: %s", err)
} else {
log.Printf("helm version: %s", ver)
}

return nil
}

// AddRepo adds a helm repo with the specified name and url
func (c *Cli) AddRepo(ctx context.Context, repo *Repository) error {
runArgs := exec.NewRunArgs("helm", "repo", "add", repo.Name, repo.Url)
_, err := c.commandRunner.Run(ctx, runArgs)
if err != nil {
return fmt.Errorf("failed to add repo %s: %w", repo.Name, err)
}

return nil
}

// UpdateRepo updates the helm repo with the specified name
func (c *Cli) UpdateRepo(ctx context.Context, repoName string) error {
runArgs := exec.NewRunArgs("helm", "repo", "update", repoName)
_, err := c.commandRunner.Run(ctx, runArgs)
if err != nil {
return fmt.Errorf("failed to add repo %s: %w", repoName, err)
}

return nil
}

// Install installs a helm release
func (c *Cli) Install(ctx context.Context, release *Release) error {
runArgs := exec.NewRunArgs("helm", "install", release.Name, release.Chart)
if release.Values != "" {
runArgs = runArgs.AppendParams("--values", release.Values)
}

_, err := c.commandRunner.Run(ctx, runArgs)
if err != nil {
return fmt.Errorf("failed to install helm chart %s: %w", release.Chart, err)
}

return nil
}

// Upgrade upgrades a helm release to the specified version
// If the release did not previously exist, it will be installed
func (c *Cli) Upgrade(ctx context.Context, release *Release) error {
runArgs := exec.NewRunArgs("helm", "upgrade", release.Name, release.Chart, "--install", "--wait")
if release.Version != "" {
runArgs = runArgs.AppendParams("--version", release.Version)
}

if release.Values != "" {
runArgs = runArgs.AppendParams("--values", release.Values)
}

if release.Namespace != "" {
runArgs = runArgs.AppendParams(
"--namespace", release.Namespace,
"--create-namespace",
)
}

_, err := c.commandRunner.Run(ctx, runArgs)
if err != nil {
return fmt.Errorf("failed to install helm chart %s: %w", release.Chart, err)
}

return nil
}

// Status returns the status of a helm release
func (c *Cli) Status(ctx context.Context, release *Release) (*StatusResult, error) {
runArgs := exec.NewRunArgs("helm", "status", release.Name, "--output", "json")
if release.Namespace != "" {
runArgs = runArgs.AppendParams("--namespace", release.Namespace)
}

runResult, err := c.commandRunner.Run(ctx, runArgs)
if err != nil {
return nil, fmt.Errorf("failed to query status for helm chart %s: %w", release.Chart, err)
}

var result *StatusResult
if err := json.Unmarshal([]byte(runResult.Stdout), &result); err != nil {
return nil, fmt.Errorf("failed to parse status for helm chart %s: %w", release.Chart, err)
}

return result, nil
}

func (cli *Cli) getClientVersion(ctx context.Context) (string, error) {
runArgs := exec.NewRunArgs("helm", "version", "--template", "{{.Version}}")
versionResult, err := cli.commandRunner.Run(ctx, runArgs)
if err != nil {
return "", fmt.Errorf("fetching helm version: %w", err)
}

return versionResult.Stdout[1:], nil
}

// StatusResult is the result of a helm status command
type StatusResult struct {
Name string `json:"name"`
Info StatusInfo `json:"info"`
Version float64 `json:"version"`
Namespace string `json:"namespace"`
}

// StatusInfo is the status information of a helm release
type StatusInfo struct {
FirstDeployed time.Time `json:"first_deployed"`
LastDeployed time.Time `json:"last_deployed"`
Status StatusKind `json:"status"`
Notes string `json:"notes"`
}

type StatusKind string

const (
// StatusKindDeployed is the status of a helm release that has been deployed
StatusKindDeployed StatusKind = "deployed"
)

0 comments on commit 153a496

Please sign in to comment.