diff --git a/clienv/wf_login.go b/clienv/wf_login.go index 8e00ba3a4..5ace7d04d 100644 --- a/clienv/wf_login.go +++ b/clienv/wf_login.go @@ -166,7 +166,7 @@ func (ce *CliEnv) loginGithub(ctx context.Context) (credentials.Credentials, err } }() - signinPage := ce.AuthURL() + "/signin/provider/github/?redirectTo=https://local.dashboard.nhost.run:8099/signin" + signinPage := ce.AuthURL() + "/signin/provider/github/?redirectTo=https://local.dashboard.local.nhost.run:8099/signin" ce.Infoln("Opening browser to sign-in") if err := openBrowser(signinPage); err != nil { return credentials.Credentials{}, err diff --git a/cmd/config/apply.go b/cmd/config/apply.go new file mode 100644 index 000000000..88dcf3dcb --- /dev/null +++ b/cmd/config/apply.go @@ -0,0 +1,99 @@ +package config + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/nhost/be/services/mimir/model" + "github.com/nhost/cli/clienv" + "github.com/urfave/cli/v2" +) + +func CommandApply() *cli.Command { + return &cli.Command{ //nolint:exhaustruct + Name: "apply", + Aliases: []string{}, + Usage: "Apply configuration to cloud project", + Action: commandApply, + Flags: []cli.Flag{ + &cli.StringFlag{ //nolint:exhaustruct + Name: flagSubdomain, + Usage: "Subdomain of the Nhost project to apply configuration to. Defaults to linked project", + Required: true, + EnvVars: []string{"NHOST_SUBDOMAIN"}, + }, + &cli.BoolFlag{ //nolint:exhaustruct + Name: flagYes, + Usage: "Skip confirmation", + EnvVars: []string{"NHOST_YES"}, + }, + }, + } +} + +func commandApply(cCtx *cli.Context) error { + ce := clienv.FromCLI(cCtx) + + proj, err := ce.GetAppInfo(cCtx.Context, cCtx.String(flagSubdomain)) + if err != nil { + return fmt.Errorf("failed to get app info: %w", err) + } + + ce.Infoln("Validating configuration...") + cfg, _, err := ValidateRemote( + cCtx.Context, + ce, + proj.GetSubdomain(), + proj.GetID(), + ) + if err != nil { + return err + } + + return Apply(cCtx.Context, ce, proj.ID, cfg, cCtx.Bool(flagYes)) +} + +func Apply( + ctx context.Context, + ce *clienv.CliEnv, + appID string, + cfg *model.ConfigConfig, + skipConfirmation bool, +) error { + if !skipConfirmation { + ce.PromptMessage( + "We are going to overwrite the project's configuration. Do you want to proceed? [y/N] ", + ) + resp, err := ce.PromptInput(false) + if err != nil { + return fmt.Errorf("failed to read input: %w", err) + } + if resp != "y" && resp != "Y" { + return errors.New("aborting") //nolint:goerr113 + } + } + + cl, err := ce.GetNhostClient(ctx) + if err != nil { + return fmt.Errorf("failed to get nhost client: %w", err) + } + + b, err := json.Marshal(cfg) + if err != nil { + return fmt.Errorf("failed to marshal config: %w", err) + } + + if _, err := cl.ReplaceConfigRawJSON( + ctx, + appID, + string(b), + ); err != nil { + return fmt.Errorf("failed to apply config: %w", err) + } + + ce.Infoln("Configuration applied successfully!") + + return nil +} diff --git a/cmd/config/config.go b/cmd/config/config.go index ec20f68d2..131a985d6 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -12,6 +12,7 @@ func Command() *cli.Command { Subcommands: []*cli.Command{ CommandDefault(), CommandExample(), + CommandApply(), CommandPull(), CommandShow(), CommandValidate(), diff --git a/cmd/config/validate.go b/cmd/config/validate.go index 7c2434895..46c9047e0 100644 --- a/cmd/config/validate.go +++ b/cmd/config/validate.go @@ -38,11 +38,18 @@ func commandValidate(cCtx *cli.Context) error { subdomain := cCtx.String(flagSubdomain) if subdomain != "" && subdomain != "local" { - return ValidateRemote( + proj, err := ce.GetAppInfo(cCtx.Context, cCtx.String(flagSubdomain)) + if err != nil { + return fmt.Errorf("failed to get app info: %w", err) + } + + _, _, err = ValidateRemote( cCtx.Context, ce, - cCtx.String(flagSubdomain), + proj.GetSubdomain(), + proj.GetID(), ) + return err } var secrets model.Secrets @@ -134,50 +141,46 @@ func ValidateRemote( ctx context.Context, ce *clienv.CliEnv, subdomain string, -) error { + appID string, +) (*model.ConfigConfig, *model.ConfigConfig, error) { cfg := &model.ConfigConfig{} //nolint:exhaustruct if err := clienv.UnmarshalFile(ce.Path.NhostToml(), cfg, toml.Unmarshal); err != nil { - return fmt.Errorf("failed to parse config: %w", err) + return nil, nil, fmt.Errorf("failed to parse config: %w", err) } schema, err := schema.New() if err != nil { - return fmt.Errorf("failed to create schema: %w", err) - } - - proj, err := ce.GetAppInfo(ctx, subdomain) - if err != nil { - return fmt.Errorf("failed to get app info: %w", err) + return nil, nil, fmt.Errorf("failed to create schema: %w", err) } ce.Infoln("Getting secrets...") cl, err := ce.GetNhostClient(ctx) if err != nil { - return fmt.Errorf("failed to get nhost client: %w", err) + return nil, nil, fmt.Errorf("failed to get nhost client: %w", err) } secretsResp, err := cl.GetSecrets( ctx, - proj.ID, + appID, ) if err != nil { - return fmt.Errorf("failed to get secrets: %w", err) + return nil, nil, fmt.Errorf("failed to get secrets: %w", err) } - if clienv.PathExists(ce.Path.Overlay(proj.GetSubdomain())) { + if clienv.PathExists(ce.Path.Overlay(subdomain)) { var err error - cfg, err = ApplyJSONPatches(*cfg, ce.Path.Overlay(proj.GetSubdomain())) + cfg, err = ApplyJSONPatches(*cfg, ce.Path.Overlay(subdomain)) if err != nil { - return fmt.Errorf("failed to apply json patches: %w", err) + return nil, nil, fmt.Errorf("failed to apply json patches: %w", err) } } secrets := respToSecrets(secretsResp.GetAppSecrets(), false) - _, err = appconfig.SecretsResolver[model.ConfigConfig](cfg, secrets, schema.Fill) + cfgSecrets, err := appconfig.SecretsResolver[model.ConfigConfig](cfg, secrets, schema.Fill) if err != nil { - return fmt.Errorf("failed to validate config: %w", err) + return nil, nil, fmt.Errorf("failed to validate config: %w", err) } ce.Infoln("Config is valid!") - return nil + return cfg, cfgSecrets, nil } diff --git a/cmd/dev/cloud.go b/cmd/dev/cloud.go new file mode 100644 index 000000000..a25e2d718 --- /dev/null +++ b/cmd/dev/cloud.go @@ -0,0 +1,300 @@ +package dev + +import ( + "context" + "errors" + "fmt" + "os" + "os/signal" + "syscall" + "text/tabwriter" + "time" + + "github.com/nhost/cli/clienv" + "github.com/nhost/cli/cmd/config" + "github.com/nhost/cli/cmd/software" + "github.com/nhost/cli/dockercompose" + "github.com/nhost/cli/nhostclient/graphql" + "github.com/urfave/cli/v2" +) + +const ( + flagSubdomain = "subdomain" + flagPostgresURL = "postgres-url" +) + +func CommandCloud() *cli.Command { + return &cli.Command{ //nolint:exhaustruct + Name: "cloud", + Aliases: []string{}, + Usage: "Start local development environment connected to an Nhost Cloud project (BETA)", + Action: commandCloud, + Flags: []cli.Flag{ + &cli.UintFlag{ //nolint:exhaustruct + Name: flagHTTPPort, + Usage: "HTTP port to listen on", + Value: defaultHTTPPort, + EnvVars: []string{"NHOST_HTTP_PORT"}, + }, + &cli.BoolFlag{ //nolint:exhaustruct + Name: flagDisableTLS, + Usage: "Disable TLS", + Value: false, + EnvVars: []string{"NHOST_DISABLE_TLS"}, + }, + &cli.BoolFlag{ //nolint:exhaustruct + Name: flagApplySeeds, + Usage: "Apply seeds. If the .nhost folder does not exist, seeds will be applied regardless of this flag", + Value: false, + EnvVars: []string{"NHOST_APPLY_SEEDS"}, + }, + &cli.UintFlag{ //nolint:exhaustruct + Name: flagsHasuraConsolePort, + Usage: "If specified, expose hasura console on this port. Not recommended", + Value: 0, + }, + &cli.StringFlag{ //nolint:exhaustruct + Name: flagDashboardVersion, + Usage: "Dashboard version to use", + Value: "nhost/dashboard:2.30.0", + EnvVars: []string{"NHOST_DASHBOARD_VERSION"}, + }, + &cli.StringFlag{ //nolint:exhaustruct + Name: flagConfigserverImage, + Hidden: true, + Value: "", + EnvVars: []string{"NHOST_CONFIGSERVER_IMAGE"}, + }, + &cli.BoolFlag{ //nolint:exhaustruct + Name: flagDownOnError, + Usage: "Skip confirmation", + EnvVars: []string{"NHOST_YES"}, + }, + &cli.StringFlag{ //nolint:exhaustruct + Name: flagCACertificates, + Usage: "Mounts and everrides path to CA certificates in the containers", + EnvVars: []string{"NHOST_CA_CERTIFICATES"}, + }, + &cli.StringFlag{ //nolint:exhaustruct + Name: flagSubdomain, + Usage: "Project's subdomain to operate on, defaults to linked project", + EnvVars: []string{"NHOST_SUBDOMAIN"}, + }, + &cli.StringFlag{ //nolint:exhaustruct + Name: flagPostgresURL, + Usage: "Postgres URL", + Required: true, + EnvVars: []string{"NHOST_POSTGRES_URL"}, + }, + }, + } +} + +func commandCloud(cCtx *cli.Context) error { + ce := clienv.FromCLI(cCtx) + + if !clienv.PathExists(ce.Path.NhostToml()) { + return errors.New( //nolint:goerr113 + "no nhost project found, please run `nhost init` or `nhost config pull`", + ) + } + if !clienv.PathExists(ce.Path.Secrets()) { + return errors.New( //nolint:goerr113 + "no secrets found, please run `nhost init` or `nhost config pull`", + ) + } + + proj, err := ce.GetAppInfo(cCtx.Context, cCtx.String(flagSubdomain)) + if err != nil { + return fmt.Errorf("failed to get app info: %w", err) + } + + configserverImage := cCtx.String(flagConfigserverImage) + if configserverImage == "" { + configserverImage = "nhost/cli:" + cCtx.App.Version + } + + applySeeds := cCtx.Bool(flagApplySeeds) + return Cloud( + cCtx.Context, + ce, + cCtx.App.Version, + cCtx.Uint(flagHTTPPort), + !cCtx.Bool(flagDisableTLS), + applySeeds, + dockercompose.ExposePorts{ + Auth: cCtx.Uint(flagAuthPort), + Storage: cCtx.Uint(flagStoragePort), + Graphql: cCtx.Uint(flagsHasuraPort), + Console: cCtx.Uint(flagsHasuraConsolePort), + Functions: cCtx.Uint(flagsFunctionsPort), + }, + cCtx.String(flagDashboardVersion), + configserverImage, + cCtx.String(flagCACertificates), + cCtx.Bool(flagDownOnError), + proj, + cCtx.String(flagPostgresURL), + ) +} + +func cloud( //nolint:funlen + ctx context.Context, + ce *clienv.CliEnv, + appVersion string, + dc *dockercompose.DockerCompose, + httpPort uint, + useTLS bool, + applySeeds bool, + ports dockercompose.ExposePorts, + dashboardVersion string, + configserverImage string, + caCertificatesPath string, + proj *graphql.AppSummaryFragment, + postgresURL string, +) error { + ctx, cancel := context.WithCancel(ctx) + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) + go func() { + <-sigChan + cancel() + }() + + ce.Infoln("Validating configuration...") + cfg, cfgSecrets, err := config.ValidateRemote( + ctx, + ce, + proj.GetSubdomain(), + proj.GetID(), + ) + if err != nil { + return fmt.Errorf("failed to validate configuration: %w", err) + } + + ctxWithTimeout, cancel := context.WithTimeout(ctx, 5*time.Second) //nolint:mnd + defer cancel() + ce.Infoln("Checking versions...") + if err := software.CheckVersions(ctxWithTimeout, ce, cfg, appVersion); err != nil { + ce.Warnln("Problem verifying recommended versions: %s", err.Error()) + } + + ce.Infoln("Setting up Nhost development environment...") + composeFile, err := dockercompose.CloudComposeFileFromConfig( + cfg, + ce.LocalSubdomain(), + proj.GetSubdomain(), + proj.GetRegion().GetName(), + cfgSecrets.Hasura.GetAdminSecret(), + postgresURL, + ce.ProjectName(), + httpPort, + useTLS, + ce.Path.NhostFolder(), + ce.Path.DotNhostFolder(), + ce.Path.Root(), + ports, + dashboardVersion, + configserverImage, + caCertificatesPath, + ) + if err != nil { + return fmt.Errorf("failed to generate docker-compose.yaml: %w", err) + } + if err := dc.WriteComposeFile(composeFile); err != nil { + return fmt.Errorf("failed to write docker-compose.yaml: %w", err) + } + + ce.Infoln("Starting Nhost development environment...") + if err = dc.Start(ctx); err != nil { + return fmt.Errorf("failed to start Nhost development environment: %w", err) + } + + ce.Infoln("Applying configuration to Nhost Cloud project...") + if err = config.Apply(ctx, ce, proj.GetID(), cfg, true); err != nil { + return fmt.Errorf("failed to apply configuration: %w", err) + } + + endpoint := fmt.Sprintf( + "https://%s.hasura.%s.nhost.run", + proj.GetSubdomain(), proj.GetRegion().GetName(), + ) + + if err := migrations(ctx, ce, dc, endpoint, applySeeds); err != nil { + return err + } + + docker := dockercompose.NewDocker() + ce.Infoln("Downloading metadata...") + if err := docker.HasuraWrapper( + ctx, + ce.LocalSubdomain(), + ce.Path.NhostFolder(), + *cfg.Hasura.Version, + "metadata", "export", + "--skip-update-check", + "--log-level", "ERROR", + "--endpoint", endpoint, + "--admin-secret", cfgSecrets.Hasura.GetAdminSecret(), + ); err != nil { + return fmt.Errorf("failed to create metadata: %w", err) + } + + ce.Infoln("Nhost development environment started.") + printCloudInfo(ce.LocalSubdomain(), httpPort, useTLS) + return nil +} + +func printCloudInfo( + subdomain string, + httpPort uint, + useTLS bool, +) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0) //nolint:mnd + fmt.Fprintf(w, "URLs:\n") + fmt.Fprintf(w, "- Console:\t\t%s\n", dockercompose.URL( + subdomain, "hasura", httpPort, useTLS)) + fmt.Fprintf(w, "- Dashboard:\t\t%s\n", dockercompose.URL( + subdomain, "dashboard", httpPort, useTLS)) + + w.Flush() +} + +func Cloud( + ctx context.Context, + ce *clienv.CliEnv, + appVersion string, + httpPort uint, + useTLS bool, + applySeeds bool, + ports dockercompose.ExposePorts, + dashboardVersion string, + configserverImage string, + caCertificatesPath string, + downOnError bool, + proj *graphql.AppSummaryFragment, + postgresURL string, +) error { + dc := dockercompose.New(ce.Path.WorkingDir(), ce.Path.DockerCompose(), ce.ProjectName()) + + if err := cloud( + ctx, + ce, + appVersion, + dc, + httpPort, + useTLS, + applySeeds, + ports, + dashboardVersion, + configserverImage, + caCertificatesPath, + proj, + postgresURL, + ); err != nil { + return upErr(ce, dc, downOnError, err) //nolint:contextcheck + } + + return nil +} diff --git a/cmd/dev/up.go b/cmd/dev/up.go index 4f0a5a7f5..a5a8cde21 100644 --- a/cmd/dev/up.go +++ b/cmd/dev/up.go @@ -135,6 +135,9 @@ func CommandUp() *cli.Command { //nolint:funlen EnvVars: []string{"NHOST_CA_CERTIFICATES"}, }, }, + Subcommands: []*cli.Command{ + CommandCloud(), + }, } } @@ -187,11 +190,12 @@ func migrations( ctx context.Context, ce *clienv.CliEnv, dc *dockercompose.DockerCompose, + endpoint string, applySeeds bool, ) error { if clienv.PathExists(filepath.Join(ce.Path.NhostFolder(), "migrations", "default")) { ce.Infoln("Applying migrations...") - if err := dc.ApplyMigrations(ctx); err != nil { + if err := dc.ApplyMigrations(ctx, endpoint); err != nil { return fmt.Errorf("failed to apply migrations: %w", err) } } else { @@ -200,7 +204,7 @@ func migrations( if clienv.PathExists(filepath.Join(ce.Path.NhostFolder(), "metadata", "version.yaml")) { ce.Infoln("Applying metadata...") - if err := dc.ApplyMetadata(ctx); err != nil { + if err := dc.ApplyMetadata(ctx, endpoint); err != nil { return fmt.Errorf("failed to apply metadata: %w", err) } } else { @@ -210,7 +214,7 @@ func migrations( if applySeeds { if clienv.PathExists(filepath.Join(ce.Path.NhostFolder(), "seeds", "default")) { ce.Infoln("Applying seeds...") - if err := dc.ApplySeeds(ctx); err != nil { + if err := dc.ApplySeeds(ctx, endpoint); err != nil { return fmt.Errorf("failed to apply seeds: %w", err) } } @@ -386,7 +390,7 @@ func up( //nolint:funlen,cyclop return fmt.Errorf("failed to start Nhost development environment: %w", err) } - if err := migrations(ctx, ce, dc, applySeeds); err != nil { + if err := migrations(ctx, ce, dc, "http://graphql:8080", applySeeds); err != nil { return err } diff --git a/dockercompose/compose_cloud.go b/dockercompose/compose_cloud.go new file mode 100644 index 000000000..65433406e --- /dev/null +++ b/dockercompose/compose_cloud.go @@ -0,0 +1,197 @@ +package dockercompose + +import ( + "fmt" + + "github.com/nhost/be/services/mimir/model" +) + +const ( + schemeHTTP = "http" + schemeHTTPS = "https" +) + +func dashboardCloud( + cfg *model.ConfigConfig, + subdomain string, + cloudSubdomain string, + cloudRegion string, + cloudAdminSecret string, + httpPort uint, + useTLS bool, + dashboardVersion string, +) *Service { + dashboard := dashboard(cfg, subdomain, dashboardVersion, httpPort, useTLS) + + dashboard.Environment["NEXT_PUBLIC_NHOST_ADMIN_SECRET"] = cloudAdminSecret + dashboard.Environment["NEXT_PUBLIC_NHOST_AUTH_URL"] = fmt.Sprintf( + "https://%s.auth.%s.nhost.run/v1", cloudSubdomain, cloudRegion, + ) + dashboard.Environment["NEXT_PUBLIC_NHOST_GRAPHQL_URL"] = fmt.Sprintf( + "https://%s.graphql.%s.nhost.run/v1", cloudSubdomain, cloudRegion, + ) + dashboard.Environment["NEXT_PUBLIC_NHOST_STORAGE_URL"] = fmt.Sprintf( + "https://%s.storage.%s.nhost.run/v1", cloudSubdomain, cloudRegion, + ) + dashboard.Environment["NEXT_PUBLIC_NHOST_HASURA_API_URL"] = fmt.Sprintf( + "https://%s.hasura.%s.nhost.run", + cloudSubdomain, cloudRegion, + ) + + return dashboard +} + +func consoleCloud( + cfg *model.ConfigConfig, + subdomain string, + cloudSubdomain string, + cloudRegion string, + cloudAdminSecret string, + clouadPostgresURL string, + httpPort uint, + useTLS bool, + nhostFolder string, + ports ExposePorts, +) (*Service, error) { + console, err := console(cfg, subdomain, httpPort, useTLS, nhostFolder, ports.Console) + if err != nil { + return nil, err + } + + scheme := schemeHTTP + if useTLS { + scheme = schemeHTTPS + } + + console.DependsOn = nil + console.Command = []string{ + "bash", "-c", + fmt.Sprintf(` + hasura-cli \ + console \ + --no-browser \ + --endpoint https://%s.hasura.%s.nhost.run \ + --address 0.0.0.0 \ + --console-port 9695 \ + --api-port %d \ + --api-host %s://%s.hasura.local.nhost.run \ + --console-hge-endpoint https://%s.hasura.%s.nhost.run`, + cloudSubdomain, cloudRegion, httpPort, scheme, subdomain, cloudSubdomain, cloudRegion), + } + + console.Environment["HASURA_GRAPHQL_ADMIN_SECRET"] = cloudAdminSecret + console.Environment["HASURA_GRAPHQL_DATABASE_URL"] = clouadPostgresURL + + return console, nil +} + +func getServicesCloud( + cfg *model.ConfigConfig, + subdomain string, + cloudSubdomain string, + cloudRegion string, + cloudAdminSecret string, + clouadPostgresURL string, + projectName string, + httpPort uint, + useTLS bool, + nhostFolder string, + dotNhostFolder string, + rootFolder string, + ports ExposePorts, + dashboardVersion string, + configserviceImage string, +) (map[string]*Service, error) { + traefik, err := traefik(subdomain, projectName, httpPort, dotNhostFolder) + if err != nil { + return nil, err + } + + console, err := consoleCloud( + cfg, + subdomain, + cloudSubdomain, + cloudRegion, + cloudAdminSecret, + clouadPostgresURL, + httpPort, + useTLS, + nhostFolder, + ports, + ) + if err != nil { + return nil, fmt.Errorf("failed to create console service: %w", err) + } + + services := map[string]*Service{ + "console": console, + "dashboard": dashboardCloud( + cfg, + subdomain, + cloudSubdomain, + cloudRegion, + cloudAdminSecret, + httpPort, + useTLS, + dashboardVersion, + ), + "traefik": traefik, + "configserver": configserver( + configserviceImage, + rootFolder, + nhostFolder, + useTLS, + ), + } + + return services, nil +} + +func CloudComposeFileFromConfig( + cfg *model.ConfigConfig, + subdomain string, + cloudSubdomain string, + cloudRegion string, + cloudAdminSecret string, + clouadPostgresURL string, + projectName string, + httpPort uint, + useTLS bool, + nhostFolder string, + dotNhostFolder string, + rootFolder string, + ports ExposePorts, + dashboardVersion string, + configserverImage string, + caCertificatesPath string, +) (*ComposeFile, error) { + services, err := getServicesCloud( + cfg, + subdomain, + cloudSubdomain, + cloudRegion, + cloudAdminSecret, + clouadPostgresURL, + projectName, + httpPort, + useTLS, + nhostFolder, + dotNhostFolder, + rootFolder, + ports, + dashboardVersion, + configserverImage, + ) + if err != nil { + return nil, err + } + + if caCertificatesPath != "" { + mountCACertificates(caCertificatesPath, services) + } + + return &ComposeFile{ + Services: services, + Volumes: nil, + }, nil +} diff --git a/dockercompose/dockercompose.go b/dockercompose/dockercompose.go index 4ae5cb02c..796dc552d 100644 --- a/dockercompose/dockercompose.go +++ b/dockercompose/dockercompose.go @@ -136,7 +136,7 @@ func (dc *DockerCompose) Wrapper(ctx context.Context, extraArgs ...string) error return nil } -func (dc *DockerCompose) ApplyMetadata(ctx context.Context) error { +func (dc *DockerCompose) ApplyMetadata(ctx context.Context, endpoint string) error { cmd := exec.CommandContext( //nolint:gosec ctx, "docker", "compose", @@ -147,7 +147,7 @@ func (dc *DockerCompose) ApplyMetadata(ctx context.Context) error { "console", "hasura-cli", "metadata", "apply", - "--endpoint", "http://graphql:8080", + "--endpoint", endpoint, "--skip-update-check", ) @@ -193,7 +193,7 @@ func (dc *DockerCompose) ReloadMetadata(ctx context.Context) error { return nil } -func (dc *DockerCompose) ApplyMigrations(ctx context.Context) error { +func (dc *DockerCompose) ApplyMigrations(ctx context.Context, endpoint string) error { cmd := exec.CommandContext( //nolint:gosec ctx, "docker", "compose", @@ -205,7 +205,7 @@ func (dc *DockerCompose) ApplyMigrations(ctx context.Context) error { "hasura-cli", "migrate", "apply", - "--endpoint", "http://graphql:8080", + "--endpoint", endpoint, "--all-databases", "--skip-update-check", ) @@ -229,7 +229,7 @@ func (dc *DockerCompose) ApplyMigrations(ctx context.Context) error { return nil } -func (dc *DockerCompose) ApplySeeds(ctx context.Context) error { +func (dc *DockerCompose) ApplySeeds(ctx context.Context, endpoint string) error { cmd := exec.CommandContext( //nolint:gosec ctx, "docker", "compose", @@ -241,7 +241,7 @@ func (dc *DockerCompose) ApplySeeds(ctx context.Context) error { "hasura-cli", "seed", "apply", - "--endpoint", "http://graphql:8080", + "--endpoint", endpoint, "--all-databases", "--skip-update-check", ) diff --git a/dockercompose/graphql.go b/dockercompose/graphql.go index 9acb2ee73..73f835e07 100644 --- a/dockercompose/graphql.go +++ b/dockercompose/graphql.go @@ -100,9 +100,9 @@ func console( //nolint:funlen ) } - scheme := "http" + scheme := schemeHTTP if useTLS { - scheme = "https" + scheme = schemeHTTPS } envars, err := appconfig.HasuraEnv( diff --git a/dockercompose/url.go b/dockercompose/url.go index 13e213f71..74ad62817 100644 --- a/dockercompose/url.go +++ b/dockercompose/url.go @@ -9,9 +9,9 @@ func URL(host, service string, port uint, useTLS bool) string { return fmt.Sprintf("http://%s.%s.local.nhost.run", host, service) } - protocol := "http" + protocol := schemeHTTP if useTLS { - protocol = "https" + protocol = schemeHTTPS } return fmt.Sprintf("%s://%s.%s.local.nhost.run:%d", protocol, host, service, port) } diff --git a/flake.lock b/flake.lock index 45f82ed16..f4a0f9878 100644 --- a/flake.lock +++ b/flake.lock @@ -62,16 +62,15 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1750076681, + "lastModified": 1750077810, "narHash": "sha256-MsaskaJ+a0O06SZ6eVLfhx+oMcO6R8PFlyBpRwnMn8E=", "owner": "nhost", "repo": "nixops", - "rev": "c5c91aef5a51cb476324478696572dd3bbac2dff", + "rev": "1ed1112322d418277f25f4a13f6b0f2f5928fa6e", "type": "github" }, "original": { "owner": "nhost", - "ref": "arch", "repo": "nixops", "type": "github" } diff --git a/nhostclient/graphql/client_gen.go b/nhostclient/graphql/client_gen.go index 298002453..a4727d0c2 100644 --- a/nhostclient/graphql/client_gen.go +++ b/nhostclient/graphql/client_gen.go @@ -450,6 +450,17 @@ func (t *GetConfigRawJSON) GetConfigRawJSON() string { return t.ConfigRawJSON } +type ReplaceConfigRawJSON struct { + ReplaceConfigRawJSON string "json:\"replaceConfigRawJSON\" graphql:\"replaceConfigRawJSON\"" +} + +func (t *ReplaceConfigRawJSON) GetReplaceConfigRawJSON() string { + if t == nil { + t = &ReplaceConfigRawJSON{} + } + return t.ReplaceConfigRawJSON +} + type ListDeployments struct { Deployments []*ListDeployments_Deployments "json:\"deployments\" graphql:\"deployments\"" } @@ -690,6 +701,29 @@ func (c *Client) GetConfigRawJSON(ctx context.Context, appID string, interceptor return &res, nil } +const ReplaceConfigRawJSONDocument = `mutation ReplaceConfigRawJSON ($appID: uuid!, $rawJSON: String!) { + replaceConfigRawJSON(appID: $appID, rawJSON: $rawJSON) +} +` + +func (c *Client) ReplaceConfigRawJSON(ctx context.Context, appID string, rawJSON string, interceptors ...clientv2.RequestInterceptor) (*ReplaceConfigRawJSON, error) { + vars := map[string]any{ + "appID": appID, + "rawJSON": rawJSON, + } + + var res ReplaceConfigRawJSON + if err := c.Client.Post(ctx, "ReplaceConfigRawJSON", ReplaceConfigRawJSONDocument, &res, vars, interceptors...); err != nil { + if c.Client.ParseDataWhenErrors { + return &res, err + } + + return nil, err + } + + return &res, nil +} + const ListDeploymentsDocument = `query ListDeployments ($appID: uuid!) { deployments(where: {appId:{_eq:$appID}}, order_by: {deploymentStartedAt:desc}, limit: 10) { id @@ -1032,6 +1066,7 @@ var DocumentOperationNames = map[string]string{ GetOrganizationsAndWorkspacesAppsDocument: "GetOrganizationsAndWorkspacesApps", GetHasuraAdminSecretDocument: "GetHasuraAdminSecret", GetConfigRawJSONDocument: "GetConfigRawJSON", + ReplaceConfigRawJSONDocument: "ReplaceConfigRawJSON", ListDeploymentsDocument: "ListDeployments", GetDeploymentLogsDocument: "GetDeploymentLogs", InsertDeploymentDocument: "InsertDeployment", diff --git a/nhostclient/graphql/query/config.graphql b/nhostclient/graphql/query/config.graphql index 28574f6d7..84b7ad2f1 100644 --- a/nhostclient/graphql/query/config.graphql +++ b/nhostclient/graphql/query/config.graphql @@ -1,3 +1,7 @@ query GetConfigRawJSON($appID: uuid!) { configRawJSON(appID: $appID, resolve: false) } + +mutation ReplaceConfigRawJSON($appID: uuid!, $rawJSON: String!) { + replaceConfigRawJSON(appID: $appID, rawJSON: $rawJSON) +}