Skip to content

Instantly share code, notes, and snippets.

@ajbouh
Created March 10, 2022 18:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ajbouh/292554473480cdbb24595e67f69445ba to your computer and use it in GitHub Desktop.
Save ajbouh/292554473480cdbb24595e67f69445ba to your computer and use it in GitHub Desktop.
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"github.com/logrusorgru/aurora"
"github.com/.../fly/internal/apiext"
"github.com/.../fly/internal/appconfig"
"github.com/.../fly/internal/client"
"github.com/.../fly/internal/render"
"github.com/.../fly/internal/secrets"
"github.com/superfly/flyctl/api"
)
var (
appName = flag.String("app_name", os.Getenv("FLY_APP_NAME"), "Name of the app")
orgID = flag.String("organization_id", os.Getenv("FLY_ORGANIZATION_ID"), "Organization ID to deploy the app under")
orgSlug = flag.String("organization_slug", os.Getenv("FLY_ORGANIZATION_SLUG"), "Organization slug to deploy the app under")
appConfigFile = flag.String("app_config", "", "Path to app config file")
secretsFormat = flag.String("secrets_format", "json", "Format to use when parsing secrets")
secretsCommand = flag.String("secrets_command", "", "Command that writes secrets to stdout")
)
func getSecretsDecoder() (secrets.Decoder, error) {
switch *secretsFormat {
case "json":
return secrets.DecodeFromJSON, nil
case "env":
return secrets.DecodeFromEnvEncoding, nil
default:
return nil, fmt.Errorf("invalid secrets_format: %s", *secretsFormat)
}
}
func readSecrets() (map[string]string, error) {
if *secretsCommand == "" {
return map[string]string{}, nil
}
parsedSecretsCommand := []string{}
err := json.Unmarshal([]byte(*secretsCommand), &parsedSecretsCommand)
if err != nil {
return nil, err
}
decoder, err := getSecretsDecoder()
if err != nil {
return nil, err
}
return secrets.ReadFromSubprocess(parsedSecretsCommand, decoder)
}
func run(ctx context.Context) (err error) {
apiClient := client.FromContext(ctx).API()
log.Print("Loading app config")
appConfig, err := appconfig.LoadAppConfig(*appConfigFile)
if err != nil {
return err
}
imageRef := appConfig.Image()
if imageRef == "" {
return fmt.Errorf("app config missing image field")
}
log.Print("Reading secrets")
declaredSecrets, err := readSecrets()
if err != nil {
return err
}
var app *api.App
if *orgID == "" {
log.Printf("Resolving organization ID for slug %s", *orgSlug)
org, err := apiClient.GetOrganizationBySlug(ctx, *orgSlug)
if err != nil {
return err
}
*orgID = org.ID
}
log.Print("Ensuring app")
app, err = apiext.EnsureApp(ctx, apiClient, *orgID, *appName)
if err != nil {
return err
}
log.Print("Validating app configuration")
parsedCfg, err := apiClient.ParseConfig(ctx, *appName, appConfig.Definition)
if err != nil {
if parsedCfg == nil {
// No error data has been returned
return fmt.Errorf("not possible to validate configuration: server returned %s", err)
}
for _, error := range parsedCfg.Errors {
log.Print("deploy ", aurora.Red("✘").String(), error)
}
return err
}
appConfig.Definition = parsedCfg.Definition
if parsedCfg.Valid && len(parsedCfg.Services) > 0 {
log.Print(aurora.Bold("Services"))
for _, svc := range parsedCfg.Services {
log.Print(svc.Description)
}
}
log.Print("Validating app configuration done")
_, err = apiext.EnsureSecrets(ctx, apiClient, *appName, declaredSecrets)
if err != nil {
return err
}
deployInput := api.DeployImageInput{
AppID: *appName,
Image: imageRef,
}
if appConfig != nil && len(appConfig.Definition) > 0 {
deployInput.Definition = api.DefinitionPtr(appConfig.Definition)
}
fmt.Fprintf(os.Stderr, "Deploy %#v\n", appConfig)
release, releaseCommand, err := apiClient.DeployImage(ctx, deployInput)
if err != nil {
return err
}
fmt.Fprintf(os.Stderr, "Release v%d created\n", release.Version)
if releaseCommand != nil {
log.Print("Release command detected: this new release will not be available until the command succeeds.")
}
if err == nil {
return render.JSON(os.Stdout, app)
}
return err
}
func main() {
flag.Parse()
ctx := context.Background()
api.SetBaseURL("https://api.fly.io")
apiClient := client.New()
ctx = client.NewContext(ctx, apiClient)
err := run(ctx)
if err != nil {
log.Fatal(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment