Skip to content
Merged
56 changes: 40 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
![Draft logo](./ghAssets/[email protected])

<div id="top"></div>
<br />
<div align="center">
Expand All @@ -21,6 +22,7 @@
</div>

## Installation

### Homebrew

1. Run the following commands
Expand All @@ -43,7 +45,7 @@ Draft is a tool made for users who are just getting started with Kubernetes or w

### `draft create`

In our directory that holds our application, we can run the CLI command ‘draft create’. Draft create will walk you through a series of questions prompting you on your application specification. At the end of it, you will have a Dockerfile as well as Kubernetes manifests to deploy your application. Below is a picture of running the Draft create command on our [Contoso Air repository](https://github.com/microsoft/ContosoAir).
In our directory that holds our application, we can run the CLI command ‘draft create’. Draft create will walk you through a series of questions prompting you on your application specification. At the end of it, you will have a Dockerfile as well as Kubernetes manifests to deploy your application. Below is a picture of running the Draft create command on our [Contoso Air repository](https://github.com/Azure-Samples/contoso-air).

![example of draft create command showing the prompt "select k8s deployment type" with three options "helm", "kustomize", and "manifests"](./ghAssets/draft-create.png)

Expand Down Expand Up @@ -71,11 +73,26 @@ Draft validate scans your manifests and populates warnings messages in your code

![screenshot of draft-validate](./ghAssets/draft-validate.png)

### `draft distribute`

The `draft distribute` command generates [KubeFleet](https://kubefleet.dev) ClusterResourcePlacement manifests to distribute your application resources across multiple Kubernetes clusters managed by KubeFleet. This command is specifically designed for multi-cluster resource placement scenarios and requires existing files created using `draft create`. [Read the documentation for this command](docs/kubefleet-clusterresourceplacement.md) for more details.

Example usage:

```bash
draft distribute --variable CRP_NAME=demo-crp \
--variable RESOURCE_SELECTOR_NAME=demo-namespace \
--variable PLACEMENT_TYPE=PickAll \
--variable PARTOF=my-project
```

### `draft info`

The `draft info` command prints information about supported languages and deployment types.

Example output (for brevity, only the first supported language is shown):
```

```json
{
"supportedLanguages": [
{
Expand All @@ -97,6 +114,7 @@ Example output (for brevity, only the first supported language is shown):
]
}
```

<!-- ABOUT THE PROJECT -->

## About The Project
Expand All @@ -105,20 +123,23 @@ Draft makes it easier for developers to get started building apps that run on Ku

### Commands

- `draft create` adds the minimum required Dockerfile and manifest files for your deployment to the project directory.
- Supported deployment types: Helm, Kustomize, Kubernetes manifest.
- `draft setup-gh` automates the GitHub OIDC setup process for your project.
- `draft generate-workflow` generates a GitHub Actions workflow for automatic build and deploy to a Kubernetes cluster.
- `draft update` automatically make your application to be internet accessible.
- `draft validate` scan your manifests to see if they are following Kubernetes best practices.
- `draft info` print supported language and field information in json format.
* `draft create` adds the minimum required Dockerfile and manifest files for your deployment to the project directory.
* Supported deployment types: Helm, Kustomize, Kubernetes manifest.
* `draft setup-gh` automates the GitHub OIDC setup process for your project.
* `draft generate-workflow` generates a GitHub Actions workflow for automatic build and deploy to a Kubernetes cluster.
* `draft update` automatically make your application to be internet accessible.
* `draft distribute` distributes your application resources across Kubernetes clusters using [KubeFleet](https://kubefleet.dev/) ClusterResourcePlacement manifests.
* `draft validate` scan your manifests to see if they are following Kubernetes best practices.
* `draft info` print supported language and field information in json format.

Use `draft [command] --help` for more information about a command.

### Dry Run

The following flags can be used for enabling dry running, which is currently supported by the following commands: `create`
- ` --dry-run` enables dry run mode in which no files are written to disk
- `--dry-run-file` specifies a file to write the dry run summary in json format into

* `--dry-run` enables dry run mode in which no files are written to disk
* `--dry-run-file` specifies a file to write the dry run summary in json format into

```json
// Example dry run output
Expand Down Expand Up @@ -146,6 +167,7 @@ The following flags can be used for enabling dry running, which is currently sup
]
}
```

## Install from Source

### Prerequisites
Expand Down Expand Up @@ -177,26 +199,28 @@ go version
mv draft $HOME/go/bin/
```


## Draft as a Dependency

If you are looking to leverage Draft's file generation capabilities and templating within another project instead of using the CLI, you have two options: importing the Draft go packages, and wrapping the binary

### Importing Draft Go Packages

This option will provide the cleanest integration, as it directly builds Draft into your project. However, it requires that your project is written in Go.

Dockerfiles can be generated following the example in [examples/dockerfile.go](https://github.com/Azure/draft/blob/main/example/dockerfile.go)

Deployment files can be generated following the example in [examples/deployment.go](https://github.com/Azure/draft/blob/main/example/deployment.go)

### Wrapping the Binary

For projects written in languages other than Go, or for projects that prefer to not import the packages directly, you can wrap the Draft binary.

Several features have been implemented to make consuming draft as easy as possible:
- `draft info` prints supported language and field information in json format for easy parsing
- `--dry-run` and `--dry-run-file` flags can be used on the `create` and `update` commands to generate a summary of the files that would be written to disk, and the variables that would be used in the templates
- `draft update` and `draft create` accept a repeatable `--variable` flag that can be used to set template variables
- `draft create` takes a `--create-config` flag that can be used to input variables through a yaml file instead of interactively

* `draft info` prints supported language and field information in json format for easy parsing
* `--dry-run` and `--dry-run-file` flags can be used on the `create`, `update` and `ditribute` commands to generate a summary of the files that would be written to disk, and the variables that would be used in the templates
* `draft update`, `draft create` and `draft distribute` accept a repeatable `--variable` flag that can be used to set template variables
* `draft create` takes a `--create-config` flag that can be used to input variables through a yaml file instead of interactively

## Introduction Videos

Expand Down
131 changes: 131 additions & 0 deletions cmd/distribute.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package cmd

import (
"encoding/json"
"errors"
"fmt"
"os"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/Azure/draft/pkg/cmdhelpers"
"github.com/Azure/draft/pkg/config"
dryrunpkg "github.com/Azure/draft/pkg/dryrun"
"github.com/Azure/draft/pkg/handlers"
"github.com/Azure/draft/pkg/templatewriter"
"github.com/Azure/draft/pkg/templatewriter/writers"
)

type distributeCmd struct {
dest string
provider string
addon string
flagVariables []string
templateWriter templatewriter.TemplateWriter
templateVariableRecorder config.TemplateVariableRecorder
}

var distributeDryRunRecorder *dryrunpkg.DryRunRecorder

func newDistributeCmd() *cobra.Command {
dc := &distributeCmd{}
// distributeCmd represents the distribute command
var cmd = &cobra.Command{
Use: "distribute",
Short: "Distributes your application resources across Kubernetes clusters using Kubefleet",
Long: `This command generates Kubefleet ClusterResourcePlacement manifests to distribute your application
resources across multiple Kubernetes clusters managed by Kubefleet.`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := dc.run(); err != nil {
return err
}
log.Info("Draft has successfully created your Kubefleet ClusterResourcePlacement manifest for resource distribution 😃")
return nil
},
}
f := cmd.Flags()
f.StringVarP(&dc.dest, "destination", "d", ".", "specify the path to the project directory")
f.StringVarP(&dc.provider, "provider", "p", "azure", "cloud provider")
f.StringVarP(&dc.addon, "addon", "a", "kubefleet-clusterresourceplacement", "kubefleet addon name")
f.StringArrayVarP(&dc.flagVariables, "variable", "", []string{}, "pass template variables (e.g. --variable CRP_NAME=demo-crp --variable PLACEMENT_TYPE=PickAll)")

dc.templateWriter = &writers.LocalFSWriter{}

return cmd
}

func (dc *distributeCmd) run() error {
flagVariablesMap = flagVariablesToMap(dc.flagVariables)

if dryRun {
distributeDryRunRecorder = dryrunpkg.NewDryRunRecorder()
dc.templateVariableRecorder = distributeDryRunRecorder
dc.templateWriter = distributeDryRunRecorder
}

updatedDest, err := cmdhelpers.GetAddonDestPath(dc.dest)
if err != nil {
log.Errorf("error getting addon destination path: %s", err.Error())
return err
}

// Default to kubefleet-clusterresourceplacement addon, but allow other kubefleet addons
templateName := "kubefleet-clusterresourceplacement"
if dc.addon != "" {
templateName = dc.addon
}

// Validate that the addon is a kubefleet addon
if templateName != "kubefleet-clusterresourceplacement" {
return fmt.Errorf("distribute command only supports kubefleet addons, got: %s", templateName)
}

addonTemplate, err := handlers.GetTemplate(templateName, "", updatedDest, dc.templateWriter)
if err != nil {
log.Errorf("error getting kubefleet addon template: %s", err.Error())
return err
}
if addonTemplate == nil {
return errors.New("DraftConfig is nil")
}

addonTemplate.Config.VariableMapToDraftConfig(flagVariablesMap)

err = cmdhelpers.PromptAddonValues(dc.dest, addonTemplate.Config)
if err != nil {
return err
}

if dryRun {
for _, variable := range addonTemplate.Config.Variables {
dc.templateVariableRecorder.Record(variable.Name, variable.Value)
}
}

err = addonTemplate.Generate()
if err != nil {
log.Errorf("error generating kubefleet addon template: %s", err.Error())
return err
}

if dryRun {
dryRunText, err := json.MarshalIndent(distributeDryRunRecorder.DryRunInfo, "", TWO_SPACES)
if err != nil {
return err
}
fmt.Println(string(dryRunText))
if dryRunFile != "" {
log.Printf("writing dry run info to file %s", dryRunFile)
err = os.WriteFile(dryRunFile, dryRunText, 0644)
if err != nil {
return err
}
}
}
return err
}

func init() {
rootCmd.AddCommand(newDistributeCmd())
}
22 changes: 14 additions & 8 deletions cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,31 +70,37 @@ func (uc *updateCmd) run() error {
return err
}

ingressTemplate, err := handlers.GetTemplate("app-routing-ingress", "", updatedDest, uc.templateWriter)
// Use the specified addon template, default to app-routing-ingress for backward compatibility
templateName := "app-routing-ingress"
if uc.addon != "" {
templateName = uc.addon
}

addonTemplate, err := handlers.GetTemplate(templateName, "", updatedDest, uc.templateWriter)
if err != nil {
log.Errorf("error getting ingress template: %s", err.Error())
log.Errorf("error getting addon template: %s", err.Error())
return err
}
if ingressTemplate == nil {
if addonTemplate == nil {
return errors.New("DraftConfig is nil")
}

ingressTemplate.Config.VariableMapToDraftConfig(flagVariablesMap)
addonTemplate.Config.VariableMapToDraftConfig(flagVariablesMap)

err = cmdhelpers.PromptAddonValues(uc.dest, ingressTemplate.Config)
err = cmdhelpers.PromptAddonValues(uc.dest, addonTemplate.Config)
if err != nil {
return err
}

if dryRun {
for _, variable := range ingressTemplate.Config.Variables {
for _, variable := range addonTemplate.Config.Variables {
uc.templateVariableRecorder.Record(variable.Name, variable.Value)
}
}

err = ingressTemplate.Generate()
err = addonTemplate.Generate()
if err != nil {
log.Errorf("error generating ingress template: %s", err.Error())
log.Errorf("error generating addon template: %s", err.Error())
return err
}

Expand Down
Loading