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

Adds support for Helm and Kustomize for AKS service target #3196

Merged
merged 2 commits into from Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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"`
wbreza marked this conversation as resolved.
Show resolved Hide resolved
}

// 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"
)