Skip to content

Commit

Permalink
Adds AKS bicep infra
Browse files Browse the repository at this point in the history
  • Loading branch information
wbreza committed Feb 10, 2023
1 parent c4a201e commit 439a58b
Show file tree
Hide file tree
Showing 53 changed files with 5,884 additions and 46 deletions.
28 changes: 14 additions & 14 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
// 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": "Attach to Process",
"type": "go",
"request": "attach",
"mode": "local",
"processId": "${command:pickGoProcess}"
}
]
}
// 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": "Attach to Process",
"type": "go",
"request": "attach",
"mode": "local",
"processId": "${command:pickGoProcess}"
}
]
}
5 changes: 5 additions & 0 deletions cli/azd/pkg/azure/resource_ids.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ func WebsiteRID(subscriptionId, resourceGroupName, websiteName string) string {
return returnValue
}

func AksRID(subscriptionId, resourceGroupName, clusterName string) string {
returnValue := fmt.Sprintf("%s/providers/Microsoft.ContainerService/managedClusters/%s", ResourceGroupRID(subscriptionId, resourceGroupName), clusterName)
return returnValue
}

func ContainerAppRID(subscriptionId, resourceGroupName, containerAppName string) string {
returnValue := fmt.Sprintf(
"%s/providers/Microsoft.App/containerApps/%s",
Expand Down
3 changes: 3 additions & 0 deletions cli/azd/pkg/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ const TenantIdEnvVarName = "AZURE_TENANT_ID"
// to.
const ContainerRegistryEndpointEnvVarName = "AZURE_CONTAINER_REGISTRY_ENDPOINT"

// ContainerRegistryEndpointEnvVarName is the name of they key used to store the endpoint of the container registry to push to.
const AksClusterEnvVarName = "AZURE_AKS_CLUSTER_NAME"

// ResourceGroupEnvVarName is the name of the azure resource group that should be used for deployments
const ResourceGroupEnvVarName = "AZURE_RESOURCE_GROUP"

Expand Down
11 changes: 9 additions & 2 deletions cli/azd/pkg/exec/command_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,14 @@ func (r *commandRunner) Run(ctx context.Context, args RunArgs) (RunResult, error

cmd.Dir = args.Cwd

var stdin, stdout, stderr bytes.Buffer
var stdin io.Reader
if args.StdIn != nil {
stdin = args.StdIn
} else {
stdin = new(bytes.Buffer)
}

var stdout, stderr bytes.Buffer

cmd.Env = appendEnv(args.Env)

Expand All @@ -67,7 +74,7 @@ func (r *commandRunner) Run(ctx context.Context, args RunArgs) (RunResult, error
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
} else {
cmd.Stdin = &stdin
cmd.Stdin = stdin
cmd.Stdout = &stdout
cmd.Stderr = &stderr

Expand Down
8 changes: 8 additions & 0 deletions cli/azd/pkg/exec/runargs.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ type RunArgs struct {

// When set will attach commands to std input/output
Interactive bool

// When set will call the command with the specified StdIn
StdIn io.Reader
}

// NewRunArgs creates a new instance with the specified cmd and args
Expand Down Expand Up @@ -81,3 +84,8 @@ func (b RunArgs) WithDebug(debug bool) RunArgs {
b.Debug = debug
return b
}

func (b RunArgs) WithStdIn(stdIn io.Reader) RunArgs {
b.StdIn = stdIn
return b
}
6 changes: 6 additions & 0 deletions cli/azd/pkg/infra/azure_resource_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const (
AzureResourceTypeCacheForRedis AzureResourceType = "Microsoft.Cache/redis"
AzureResourceTypePostgreSqlServer AzureResourceType = "Microsoft.DBforPostgreSQL/flexibleServers"
AzureResourceTypeCDNProfile AzureResourceType = "Microsoft.Cdn/profiles"
AzureResourceTypeContainerRegistry AzureResourceType = "Microsoft.ContainerRegistry/registries"
AzureResourceTypeManagedCluster AzureResourceType = "Microsoft.ContainerService/managedClusters"
)

const resourceLevelSeparator = "/"
Expand Down Expand Up @@ -72,6 +74,10 @@ func GetResourceTypeDisplayName(resourceType AzureResourceType) string {
return "Azure Database for PostgreSQL flexible server"
case AzureResourceTypeCDNProfile:
return "Azure Front Door / CDN profile"
case AzureResourceTypeContainerRegistry:
return "Container Registry"
case AzureResourceTypeManagedCluster:
return "AKS Managed Cluster"
}

return ""
Expand Down
4 changes: 2 additions & 2 deletions cli/azd/pkg/project/framework_service_docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type DockerProjectOptions struct {
type dockerProject struct {
config *ServiceConfig
env *environment.Environment
docker *docker.Docker
docker docker.Docker
framework FrameworkService
}

Expand Down Expand Up @@ -66,7 +66,7 @@ func (p *dockerProject) Initialize(ctx context.Context) error {
func NewDockerProject(
config *ServiceConfig,
env *environment.Environment,
docker *docker.Docker,
docker docker.Docker,
framework FrameworkService,
) FrameworkService {
return &dockerProject{
Expand Down
9 changes: 8 additions & 1 deletion cli/azd/pkg/project/service_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/tools"
"github.com/azure/azure-dev/cli/azd/pkg/tools/azcli"
"github.com/azure/azure-dev/cli/azd/pkg/tools/docker"
"github.com/azure/azure-dev/cli/azd/pkg/tools/kubectl"
"github.com/azure/azure-dev/cli/azd/pkg/tools/swa"
)

Expand Down Expand Up @@ -124,6 +125,12 @@ func (sc *ServiceConfig) GetServiceTarget(
target, err = NewFunctionAppTarget(sc, env, resource, azCli)
case string(StaticWebAppTarget):
target, err = NewStaticWebAppTarget(sc, env, resource, azCli, swa.NewSwaCli(commandRunner))
case string(AksTarget):
containerService, err := azCli.ContainerService(ctx, env.GetSubscriptionId())
if err != nil {
return nil, err
}
target = NewAksTarget(sc, env, resource, azCli, containerService, kubectl.NewKubectl(commandRunner), docker.NewDocker(commandRunner))
default:
return nil, fmt.Errorf("unsupported host '%s' for service '%s'", sc.Host, sc.Name)
}
Expand Down Expand Up @@ -154,7 +161,7 @@ func (sc *ServiceConfig) GetFrameworkService(
}

// For containerized applications we use a nested framework service
if sc.Host == string(ContainerAppTarget) {
if sc.Host == string(ContainerAppTarget) || sc.Host == string(AksTarget) {
sourceFramework := frameworkService
frameworkService = NewDockerProject(sc, env, docker.NewDocker(commandRunner), sourceFramework)
}
Expand Down
8 changes: 2 additions & 6 deletions cli/azd/pkg/project/service_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
ContainerAppTarget ServiceTargetKind = "containerapp"
AzureFunctionTarget ServiceTargetKind = "function"
StaticWebAppTarget ServiceTargetKind = "staticwebapp"
AksTarget ServiceTargetKind = "aks"
)

type ServiceDeploymentResult struct {
Expand Down Expand Up @@ -89,10 +90,5 @@ func resourceTypeMismatchError(
// As an example, ContainerAppTarget is able to provision the container app as part of deployment,
// and thus returns true.
func (st ServiceTargetKind) SupportsDelayedProvisioning() bool {
return st == ContainerAppTarget
return st == ContainerAppTarget || st == AksTarget
}

var _ ServiceTarget = &appServiceTarget{}
var _ ServiceTarget = &containerAppTarget{}
var _ ServiceTarget = &functionAppTarget{}
var _ ServiceTarget = &staticWebAppTarget{}
177 changes: 177 additions & 0 deletions cli/azd/pkg/project/service_target_aks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package project

import (
"context"
"fmt"
"log"
"path/filepath"
"time"

"github.com/azure/azure-dev/cli/azd/pkg/azure"
"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/tools"
"github.com/azure/azure-dev/cli/azd/pkg/tools/azcli"
"github.com/azure/azure-dev/cli/azd/pkg/tools/docker"
"github.com/azure/azure-dev/cli/azd/pkg/tools/kubectl"
)

type aksTarget struct {
config *ServiceConfig
env *environment.Environment
scope *environment.TargetResource
containerService azcli.ContainerServiceClient
az azcli.AzCli
docker docker.Docker
kubectl kubectl.KubectlCli
}

func (t *aksTarget) RequiredExternalTools() []tools.ExternalTool {
return []tools.ExternalTool{t.docker}
}

func (t *aksTarget) Deploy(ctx context.Context, azdCtx *azdcontext.AzdContext, path string, progress chan<- string) (ServiceDeploymentResult, error) {
// Login to AKS cluster
namespace := t.config.Project.Name
clusterName, has := t.env.Values[environment.AksClusterEnvVarName]
if !has {
return ServiceDeploymentResult{}, fmt.Errorf("could not determine AKS cluster, ensure %s is set as an output of your infrastructure", environment.AksClusterEnvVarName)
}

log.Printf("getting AKS credentials %s\n", clusterName)
progress <- "Getting AKS credentials"
credentials, err := t.containerService.GetAdminCredentials(ctx, t.scope.ResourceGroupName(), clusterName)
if err != nil {
return ServiceDeploymentResult{}, err
}

kubeConfigManager, err := kubectl.NewKubeConfigManager(t.kubectl)
if err != nil {
return ServiceDeploymentResult{}, err
}

kubeConfig, err := kubectl.ParseKubeConfig(ctx, credentials.Kubeconfigs[0].Value)
if err != nil {
return ServiceDeploymentResult{}, fmt.Errorf("failed parsing kube config: %w", err)
}

if err := kubeConfigManager.SaveKubeConfig(ctx, clusterName, kubeConfig); err != nil {
return ServiceDeploymentResult{}, fmt.Errorf("failed saving kube config: %w", err)
}

if err := kubeConfigManager.MergeConfigs(ctx, "config", "config", clusterName); err != nil {
return ServiceDeploymentResult{}, fmt.Errorf("failed merging kube configs: %w", err)
}

if _, err := t.kubectl.ConfigUseContext(ctx, clusterName, nil); err != nil {
return ServiceDeploymentResult{}, fmt.Errorf("failed using kube context '%s', %w", clusterName, err)
}

kubeFlags := kubectl.KubeCliFlags{
Namespace: namespace,
DryRun: "client",
Output: "yaml",
}

progress <- "Creating k8s namespace"
namespaceResult, err := t.kubectl.CreateNamespace(ctx, namespace, &kubectl.KubeCliFlags{DryRun: "client", Output: "yaml"})
if err != nil {
return ServiceDeploymentResult{}, fmt.Errorf("failed creating kube namespace: %w", err)
}

_, err = t.kubectl.ApplyPipe(ctx, *namespaceResult, nil)
if err != nil {
return ServiceDeploymentResult{}, fmt.Errorf("failed applying kube namespace: %w", err)
}

progress <- "Creating k8s secrets"
secrets := t.env.Environ()
secretResult, err := t.kubectl.CreateSecretGenericFromLiterals(ctx, "azd", secrets, &kubeFlags)
if err != nil {
return ServiceDeploymentResult{}, fmt.Errorf("failed setting kube secrets: %w", err)
}

_, err = t.kubectl.ApplyPipe(ctx, *secretResult, nil)
if err != nil {
return ServiceDeploymentResult{}, fmt.Errorf("failed applying kube secrets: %w", err)
}

// Login to container registry.
loginServer, has := t.env.Values[environment.ContainerRegistryEndpointEnvVarName]
if !has {
return ServiceDeploymentResult{}, fmt.Errorf("could not determine container registry endpoint, ensure %s is set as an output of your infrastructure", environment.ContainerRegistryEndpointEnvVarName)
}

log.Printf("logging into registry %s\n", loginServer)

progress <- "Logging into container registry"
if err := t.az.LoginAcr(ctx, t.docker, t.env.GetSubscriptionId(), loginServer); err != nil {
return ServiceDeploymentResult{}, fmt.Errorf("logging into registry '%s': %w", loginServer, err)
}

resourceName := t.scope.ResourceName()
if resourceName == "" {
resourceName = t.config.Name
}

tags := []string{
fmt.Sprintf("%s/%s/%s:azdev-deploy-%d", loginServer, t.config.Project.Name, resourceName, time.Now().Unix()),
fmt.Sprintf("%s/%s/%s:latest", loginServer, t.config.Project.Name, resourceName),
}

for _, tag := range tags {
// Tag image.
log.Printf("tagging image %s as %s", path, tag)
progress <- "Tagging image"
if err := t.docker.Tag(ctx, t.config.Path(), path, tag); err != nil {
return ServiceDeploymentResult{}, fmt.Errorf("tagging image: %w", err)
}

// Push image.
progress <- "Pushing container image"
if err := t.docker.Push(ctx, t.config.Path(), tag); err != nil {
return ServiceDeploymentResult{}, fmt.Errorf("pushing image: %w", err)
}
}

progress <- "Applying k8s manifests"
_, err = t.kubectl.ApplyFiles(ctx, filepath.Join(t.config.RelativePath, "manifests"), &kubectl.KubeCliFlags{Namespace: namespace})
if err != nil {
return ServiceDeploymentResult{}, fmt.Errorf("failed applying kube manifests: %w", err)
}

endpoints, err := t.Endpoints(ctx)
if err != nil {
return ServiceDeploymentResult{}, err
}

return ServiceDeploymentResult{
TargetResourceId: azure.ContainerAppRID(t.env.GetSubscriptionId(), t.scope.ResourceGroupName(), t.scope.ResourceName()),
Kind: ContainerAppTarget,
Details: nil,
Endpoints: endpoints,
}, nil
}

func (t *aksTarget) Endpoints(ctx context.Context) ([]string, error) {
// TODO Update
return []string{"https://aks.azure.com/sample"}, nil
// containerAppProperties, err := t.cli.GetContainerAppProperties(ctx, t.env.GetSubscriptionId(), t.scope.ResourceGroupName(), t.scope.ResourceName())
// if err != nil {
// return nil, fmt.Errorf("fetching service properties: %w", err)
// }

// return []string{fmt.Sprintf("https://%s/", containerAppProperties.Properties.Configuration.Ingress.Fqdn)}, nil
}

func NewAksTarget(config *ServiceConfig, env *environment.Environment, scope *environment.TargetResource, azCli azcli.AzCli, containerService azcli.ContainerServiceClient, kubectlCli kubectl.KubectlCli, docker docker.Docker) ServiceTarget {
return &aksTarget{
config: config,
env: env,
scope: scope,
az: azCli,
containerService: containerService,
docker: docker,
kubectl: kubectlCli,
}
}
6 changes: 3 additions & 3 deletions cli/azd/pkg/project/service_target_containerapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type containerAppTarget struct {
env *environment.Environment
resource *environment.TargetResource
cli azcli.AzCli
docker *docker.Docker
docker docker.Docker
console input.Console
commandRunner exec.CommandRunner
accountManager account.Manager
Expand Down Expand Up @@ -70,7 +70,7 @@ func (at *containerAppTarget) Deploy(
log.Printf("logging into registry %s", loginServer)

progress <- "Logging into container registry"
if err := at.cli.LoginAcr(ctx, at.commandRunner, at.env.GetSubscriptionId(), loginServer); err != nil {
if err := at.cli.LoginAcr(ctx, at.docker, at.env.GetSubscriptionId(), loginServer); err != nil {
return ServiceDeploymentResult{}, fmt.Errorf("logging into registry '%s': %w", loginServer, err)
}

Expand Down Expand Up @@ -234,7 +234,7 @@ func NewContainerAppTarget(
env *environment.Environment,
resource *environment.TargetResource,
azCli azcli.AzCli,
docker *docker.Docker,
docker docker.Docker,
console input.Console,
commandRunner exec.CommandRunner,
accountManager account.Manager,
Expand Down
Loading

0 comments on commit 439a58b

Please sign in to comment.