Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,18 @@ require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/huh v0.7.0 // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/danieljoos/wincred v1.2.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
Expand All @@ -80,6 +84,7 @@ require (
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
Expand Down
12 changes: 12 additions & 0 deletions cli/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,8 @@ github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
Expand All @@ -665,12 +667,18 @@ github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j
github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/huh v0.7.0 h1:W8S1uyGETgj9Tuda3/JdVkc3x7DBLZYPZc4c+/rnRdc=
github.com/charmbracelet/huh v0.7.0/go.mod h1:UGC3DZHlgOKHvHC07a5vHag41zzhpPFj34U92sOmyuk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
Expand Down Expand Up @@ -702,6 +710,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
Expand Down Expand Up @@ -948,6 +958,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
Expand Down
1 change: 1 addition & 0 deletions cli/internal/api/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ import "errors"

var ErrUnauthenticated = errors.New("unauthenticated")
var ErrPreconditionFailed = errors.New("precondition failed")
var ErrNotFound = errors.New("not found")
6 changes: 5 additions & 1 deletion cli/internal/api/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ type PlatformRevisionResponse struct {
func (c *NitricApiClient) GetPlatform(team, name string, revision int) (*terraform.PlatformSpec, error) {
response, err := c.get(fmt.Sprintf("/api/platforms/%s/%s/revisions/%d", team, name, revision), true)
if err != nil {
return nil, fmt.Errorf("failed to connect to nitric auth details endpoint: %v", err)
return nil, err
}

if response.StatusCode != 200 {
if response.StatusCode == 404 {
return nil, ErrNotFound
}

return nil, fmt.Errorf("received non 200 response from nitric plugin details endpoint: %d", response.StatusCode)
}

Expand Down
9 changes: 9 additions & 0 deletions cli/internal/platforms/repository.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package platforms

import (
"errors"
"fmt"
"regexp"
"strconv"
Expand Down Expand Up @@ -36,7 +37,15 @@ func (r *PlatformRepository) GetPlatform(name string) (*terraform.PlatformSpec,
}

platformSpec, err := r.apiClient.GetPlatform(team, platform, revision)
// If its a 404, then return platform not found error
if err != nil {
if errors.Is(err, api.ErrNotFound) {
return nil, terraform.ErrPlatformNotFound
} else if errors.Is(err, api.ErrUnauthenticated) {
return nil, terraform.ErrUnauthenticated
}

// return the original error to the engine
return nil, err
}

Expand Down
1 change: 0 additions & 1 deletion cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ func createTokenStore(inj do.Injector) (*workos.KeyringTokenStore, error) {
}

func main() {

injector := do.New()

do.Provide(injector, createTokenStore)
Expand Down
92 changes: 65 additions & 27 deletions cli/pkg/app/nitric.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/nitrictech/nitric/cli/internal/platforms"
"github.com/nitrictech/nitric/cli/internal/plugins"
"github.com/nitrictech/nitric/cli/internal/simulation"
"github.com/nitrictech/nitric/cli/internal/style"
"github.com/nitrictech/nitric/cli/internal/style/colors"
"github.com/nitrictech/nitric/cli/internal/style/icons"
"github.com/nitrictech/nitric/cli/pkg/client"
Expand All @@ -36,6 +37,13 @@ type NitricApp struct {
config *config.Config
apiClient *api.NitricApiClient
fs afero.Fs
styles Styles
}

type Styles struct {
emphasize lipgloss.Style
faint lipgloss.Style
success lipgloss.Style
}

func NewNitricApp(injector do.Injector) (*NitricApp, error) {
Expand All @@ -46,7 +54,11 @@ func NewNitricApp(injector do.Injector) (*NitricApp, error) {
fs = afero.NewOsFs()
}

return &NitricApp{config: config, apiClient: apiClient, fs: fs}, nil
return &NitricApp{config: config, apiClient: apiClient, fs: fs, styles: Styles{
emphasize: lipgloss.NewStyle().Foreground(colors.Teal).Bold(true),
faint: lipgloss.NewStyle().Faint(true),
success: lipgloss.NewStyle().Foreground(colors.Teal).Bold(true),
}}, nil
}

// Templates handles the templates command logic
Expand Down Expand Up @@ -78,25 +90,23 @@ func (c *NitricApp) Templates() error {

// Init initializes nitric for an existing project, creating a nitric.yaml file if it doesn't exist
func (c *NitricApp) Init() error {
emphasize := lipgloss.NewStyle().Foreground(colors.Teal).Bold(true)

nitricYamlPath := filepath.Join(".", "nitric.yaml")
exists, _ := afero.Exists(c.fs, nitricYamlPath)

// Read the nitric.yaml file
_, err := schema.LoadFromFile(c.fs, nitricYamlPath, true)
if err == nil {
fmt.Printf("Project already initialized, run %s to edit the project\n", emphasize.Render("nitric edit"))
fmt.Printf("Project already initialized, run %s to edit the project\n", c.styles.emphasize.Render("nitric edit"))
return nil
} else if exists {
fmt.Printf("Project already initialized, but an error occurred loading %s\n", emphasize.Render("nitric.yaml"))
fmt.Printf("Project already initialized, but an error occurred loading %s\n", c.styles.emphasize.Render("nitric.yaml"))
return err
}

fmt.Printf("Welcome to %s, this command will walk you through creating a nitric.yaml file.\n", emphasize.Render("Nitric"))
fmt.Printf("Welcome to %s, this command will walk you through creating a nitric.yaml file.\n", c.styles.emphasize.Render("Nitric"))
fmt.Printf("This file is used to define your app's infrastructure, resources and deployment targets.\n")
fmt.Println()
fmt.Printf("Here we'll only cover the basics, use %s to continue editing the project.\n", emphasize.Render("nitric edit"))
fmt.Printf("Here we'll only cover the basics, use %s to continue editing the project.\n", c.styles.emphasize.Render("nitric edit"))
fmt.Println()

// Project Name Prompt
Expand Down Expand Up @@ -135,21 +145,19 @@ func (c *NitricApp) Init() error {
return err
}

successStyle := lipgloss.NewStyle().Foreground(colors.Teal).Bold(true)
faint := lipgloss.NewStyle().Faint(true)

fmt.Println(successStyle.Render(" " + icons.Check + " Project initialized!"))
fmt.Println(faint.Render(" nitric project written to " + nitricYamlPath))
fmt.Println()
fmt.Println(c.styles.success.Render(" " + icons.Check + " Project initialized!"))
fmt.Println(c.styles.faint.Render(" nitric project written to " + nitricYamlPath))

fmt.Println()
fmt.Println("Next steps:")
fmt.Println("1. Run", emphasize.Render("nitric edit"), "to start the nitric editor")
fmt.Println("1. Run", c.styles.emphasize.Render("nitric edit"), "to start the nitric editor")
fmt.Println("2. Design your app's resources and deployment targets")
fmt.Println("3. Optionally, use", emphasize.Render("nitric generate"), "to generate the client libraries for your app")
fmt.Println("4. Run", emphasize.Render("nitric dev"), "to start the development server")
fmt.Println("5. Run", emphasize.Render("nitric build"), "to build the project for a specific platform")
fmt.Println("3. Optionally, use", c.styles.emphasize.Render("nitric generate"), "to generate the client libraries for your app")
fmt.Println("4. Run", c.styles.emphasize.Render("nitric dev"), "to start the development server")
fmt.Println("5. Run", c.styles.emphasize.Render("nitric build"), "to build the project for a specific platform")
fmt.Println()
fmt.Println("For more information, see the", emphasize.Render("nitric docs"), "at", emphasize.Render("https://nitric.io/docs"))
fmt.Println("For more information, see the", c.styles.emphasize.Render("nitric docs"), "at", c.styles.emphasize.Render("https://nitric.io/docs"))

return nil
}
Expand Down Expand Up @@ -300,36 +308,66 @@ func (c *NitricApp) New(projectName string, force bool) error {

// Build handles the build command logic
func (c *NitricApp) Build() error {
// Read the nitric.yaml file
appSpec, err := schema.LoadFromFile(c.fs, "nitric.yaml", true)
if err != nil {
return err
}

platformRepository := platforms.NewPlatformRepository(c.apiClient)

// TODO:prompt for platform selection if multiple targets are specified
targetPlatform := appSpec.Targets[0]
if len(appSpec.Targets) == 0 {
nitricEdit := style.Teal("nitric edit")
fmt.Printf("No targets specified in nitric.yaml, run %s to add a target\n", nitricEdit)
return nil
}

var targetPlatform string

if len(appSpec.Targets) == 1 {
targetPlatform = appSpec.Targets[0]
} else {
targetPlatform, _, err = tui.RunSelect(appSpec.Targets, "Select a build target")
if err != nil {
if errors.Is(err, tui.ErrUserAborted) {
return nil
}
return err
}
}

if targetPlatform == "" {
return fmt.Errorf("no target platform selected")
}

platform, err := terraform.PlatformFromId(c.fs, targetPlatform, platformRepository)
if err != nil {
if errors.Is(err, terraform.ErrUnauthenticated) {
fmt.Printf("Please login first, using the %s command\n", c.styles.emphasize.Render("nitric login"))
return nil
} else if err != nil {
return err
}

fmt.Fprintf(os.Stdout, "\nBuilding application for %s\n\n", c.styles.emphasize.Render(targetPlatform))

repo := plugins.NewPluginRepository(c.apiClient)
engine := terraform.New(platform, terraform.WithRepository(repo))
// Parse the application spec
// Validate the application spec
// Build the application using the specified platform
// Handle any errors that occur during the build process

err = engine.Apply(appSpec)
stackPath, err := engine.Apply(appSpec)
if err != nil {
fmt.Print("Error applying platform: ", err)
return err
}

fmt.Println("Build completed successfully.")
fmt.Println(c.styles.success.Render(" " + icons.Check + " Terraform generated successfully"))
fmt.Println(c.styles.faint.Render(" output written to " + stackPath))

fmt.Println()
fmt.Println("Next steps:")
fmt.Println("1. Run", c.styles.emphasize.Render(fmt.Sprintf("cd %s", stackPath)), "to move to the stack directory")
fmt.Println("2. Initialize the stack", c.styles.emphasize.Render("terraform init -upgrade"))
fmt.Println("3. Optionally, preview with", c.styles.emphasize.Render("terraform plan"))
fmt.Println("4. Deploy with", c.styles.emphasize.Render("terraform apply"))

return nil
}

Expand Down
2 changes: 1 addition & 1 deletion cli/pkg/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
type Application struct {
// Targets sets platforms this application should be expected to work on
// This gives us room to move away from LCD expectations around how platforms are built
Targets []string `json:"targets" yaml:"targets" jsonschema:"required"`
Targets []string `json:"targets" yaml:"targets" jsonschema:"required,pattern=^(([a-z]+)/([a-z]+)@(\\d+)|file:([^\\s]+))$"`
Name string `json:"name" yaml:"name" jsonschema:"required"`
Description string `json:"description" yaml:"description"`

Expand Down
2 changes: 1 addition & 1 deletion engines/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "github.com/nitrictech/nitric/cli/pkg/schema"

// Common engine interface for all engines
type Engine interface {
Apply(application *schema.Application) error
Apply(application *schema.Application) (string, error)
}

type EngineConstructor = func(platform interface{}) Engine
8 changes: 8 additions & 0 deletions engines/terraform/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package terraform

import "fmt"

var (
ErrPlatformNotFound = fmt.Errorf("platform not found")
ErrUnauthenticated = fmt.Errorf("unauthenticated")
)
8 changes: 7 additions & 1 deletion engines/terraform/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"slices"
"strings"

"github.com/pkg/errors"

"github.com/spf13/afero"
"gopkg.in/yaml.v3"
)
Expand Down Expand Up @@ -180,8 +182,12 @@ func PlatformFromId(fs afero.Fs, platformId string, repositories ...PlatformRepo

for _, repository := range repositories {
platform, err := repository.GetPlatform(platformId)
if err != nil {
if errors.Is(err, ErrUnauthenticated) {
return nil, errors.Wrap(err, "unable to authenticate with platform repository, please make sure you are logged in with `nitric login`")
} else if errors.Is(err, ErrPlatformNotFound) {
continue
} else if err != nil {
return nil, fmt.Errorf("an unknown error occurred while fetching platform %s from platform repository, please try again later: %w", platformId, err)
}

return platform, nil
Expand Down
Loading
Loading