@@ -7,10 +7,12 @@ import (
77
88 "github.com/samber/lo"
99 fly "github.com/superfly/fly-go"
10+ "github.com/superfly/flyctl/flypg"
1011 "github.com/superfly/flyctl/gql"
1112 extensions_core "github.com/superfly/flyctl/internal/command/extensions/core"
1213 "github.com/superfly/flyctl/internal/command/launch/plan"
1314 "github.com/superfly/flyctl/internal/command/mpg"
15+ "github.com/superfly/flyctl/internal/command/postgres"
1416 "github.com/superfly/flyctl/internal/command/redis"
1517 "github.com/superfly/flyctl/internal/flyutil"
1618 "github.com/superfly/flyctl/internal/uiex"
@@ -74,124 +76,90 @@ func (state *launchState) createDatabases(ctx context.Context) error {
7476
7577func (state * launchState ) createFlyPostgres (ctx context.Context ) error {
7678 var (
77- io = iostreams . FromContext ( ctx )
78- pgPlan = state . Plan . Postgres . FlyPostgres
79- uiexClient = uiexutil . ClientFromContext (ctx )
79+ pgPlan = state . Plan . Postgres . FlyPostgres
80+ apiClient = flyutil . ClientFromContext ( ctx )
81+ io = iostreams . FromContext (ctx )
8082 )
8183
82- // Get org and region
83- org , err := state .Org (ctx )
84- if err != nil {
85- return err
86- }
87- region , err := state .Region (ctx )
88- if err != nil {
89- return err
90- }
91-
92- var slug string
93- if org .Slug == "personal" {
94- genqClient := flyutil .ClientFromContext (ctx ).GenqClient ()
95-
96- // For ui-ex request we need the real org slug
97- var fullOrg * gql.GetOrganizationResponse
98- if fullOrg , err = gql .GetOrganization (ctx , genqClient , org .Slug ); err != nil {
99- return fmt .Errorf ("failed fetching org: %w" , err )
100- }
101-
102- slug = fullOrg .Organization .RawSlug
103- } else {
104- slug = org .Slug
105- }
106-
107- // Create new managed Postgres cluster
108- input := uiex.CreateClusterInput {
109- Name : pgPlan .AppName ,
110- Region : region .Code ,
111- Plan : "basic" , // Default plan for now
112- OrgSlug : slug ,
113- }
114-
115- fmt .Fprintf (io .Out , "Provisioning Postgres cluster...\n " )
84+ attachToExisting := false
11685
117- response , err := uiexClient .CreateCluster (ctx , input )
118- if err != nil {
119- return fmt .Errorf ("failed creating managed postgres cluster: %w" , err )
86+ if pgPlan .AppName == "" {
87+ pgPlan .AppName = fmt .Sprintf ("%s-db" , state .appConfig .AppName )
12088 }
12189
122- // Wait for cluster to be ready
123- fmt .Fprintf (io .Out , "Waiting for cluster %s (%s) to be ready...\n " , pgPlan .AppName , response .Data .Id )
124- fmt .Fprintf (io .Out , "If this is taking too long, you can press Ctrl+C to continue with deployment.\n " )
125- fmt .Fprintf (io .Out , "You can check the status later with 'mpg status' and attach with 'mpg attach'.\n " )
126-
127- // Create a separate context for the wait loop that won't propagate cancellation
128- waitCtx := context .Background ()
129- waitCtx , cancel := context .WithCancel (waitCtx )
130- defer cancel ()
131-
132- // Channel to signal when cluster is ready
133- ready := make (chan bool , 1 )
134- errChan := make (chan error , 1 )
135-
136- // Start the wait loop in a goroutine
137- go func () {
138- for {
139- select {
140- case <- waitCtx .Done ():
141- return
142- default :
143- cluster , err := uiexClient .GetManagedClusterById (ctx , response .Data .Id )
144- if err != nil {
145- errChan <- fmt .Errorf ("failed checking cluster status: %w" , err )
146- return
147- }
148-
149- if cluster .Data .Status == "ready" {
150- ready <- true
151- return
152- }
153-
154- if cluster .Data .Status == "error" {
155- errChan <- fmt .Errorf ("cluster creation failed" )
156- return
157- }
158-
159- time .Sleep (5 * time .Second )
90+ if apps , err := apiClient .GetApps (ctx , nil ); err == nil {
91+ for _ , app := range apps {
92+ if app .Name == pgPlan .AppName {
93+ attachToExisting = true
16094 }
16195 }
162- }()
163-
164- // Wait for either ready signal, error, or context cancellation
165- select {
166- case <- ready :
167- // Cluster is ready, continue with user creation
168- case err := <- errChan :
169- return err
170- case <- ctx .Done ():
171- fmt .Fprintf (io .Out , "\n Continuing with deployment. You can check the status later with 'mpg status' and attach with 'mpg attach'.\n " )
172- // Continue with deployment even if cluster isn't ready
173- return nil
17496 }
17597
176- // Get the cluster credentials
177- cluster , err := uiexClient .GetManagedClusterById (ctx , response .Data .Id )
178- if err != nil {
179- return fmt .Errorf ("failed retrieving cluster credentials: %w" , err )
180- }
98+ if attachToExisting {
99+ // If we try to attach to a PG cluster with the usual username
100+ // format, we'll get an error (since that username already exists)
101+ // by generating a new username with a sufficiently random number
102+ // (in this case, the nanon second that the database is being attached)
103+ currentTime := time .Now ().Nanosecond ()
104+ dbUser := fmt .Sprintf ("%s-%d" , pgPlan .AppName , currentTime )
181105
182- // Set the connection string as a secret
183- secrets := map [string ]string {
184- "DATABASE_URL" : cluster .Credentials .ConnectionUri ,
185- }
106+ err := postgres .AttachCluster (ctx , postgres.AttachParams {
107+ PgAppName : pgPlan .AppName ,
108+ AppName : state .Plan .AppName ,
109+ DbUser : dbUser ,
110+ })
186111
187- client := flyutil .ClientFromContext (ctx )
188- if _ , err := client .SetSecrets (ctx , state .Plan .AppName , secrets ); err != nil {
189- return fmt .Errorf ("failed setting database secrets: %w" , err )
112+ if err != nil {
113+ msg := "Failed attaching %s to the Postgres cluster %s: %s.\n Try attaching manually with 'fly postgres attach --app %s %s'\n "
114+ fmt .Fprintf (io .Out , msg , state .Plan .AppName , pgPlan .AppName , err , state .Plan .AppName , pgPlan .AppName )
115+ return err
116+ } else {
117+ fmt .Fprintf (io .Out , "Postgres cluster %s is now attached to %s\n " , pgPlan .AppName , state .Plan .AppName )
118+ }
119+ } else {
120+ // Create new PG cluster
121+ org , err := state .Org (ctx )
122+ if err != nil {
123+ return err
124+ }
125+ region , err := state .Region (ctx )
126+ if err != nil {
127+ return err
128+ }
129+ err = postgres .CreateCluster (ctx , org , & region , & postgres.ClusterParams {
130+ PostgresConfiguration : postgres.PostgresConfiguration {
131+ Name : pgPlan .AppName ,
132+ DiskGb : pgPlan .DiskSizeGB ,
133+ InitialClusterSize : pgPlan .Nodes ,
134+ VMSize : pgPlan .VmSize ,
135+ MemoryMb : pgPlan .VmRam ,
136+ },
137+ ScaleToZero : & pgPlan .AutoStop ,
138+ Autostart : true , // TODO(Ali): Do we want this?
139+ Manager : flypg .ReplicationManager ,
140+ })
141+ if err != nil {
142+ fmt .Fprintf (io .Out , "Failed creating the Postgres cluster %s: %s\n " , pgPlan .AppName , err )
143+ } else {
144+ err = postgres .AttachCluster (ctx , postgres.AttachParams {
145+ PgAppName : pgPlan .AppName ,
146+ AppName : state .Plan .AppName ,
147+ SuperUser : true ,
148+ })
149+
150+ if err != nil {
151+ msg := "Failed attaching %s to the Postgres cluster %s: %s.\n Try attaching manually with 'fly postgres attach --app %s %s'\n "
152+ fmt .Fprintf (io .Out , msg , state .Plan .AppName , pgPlan .AppName , err , state .Plan .AppName , pgPlan .AppName )
153+ } else {
154+ fmt .Fprintf (io .Out , "Postgres cluster %s is now attached to %s\n " , pgPlan .AppName , state .Plan .AppName )
155+ }
156+ }
157+ if err != nil {
158+ const msg = "Error creating Postgres database. Be warned that this may affect deploys"
159+ fmt .Fprintln (io .Out , io .ColorScheme ().Red (msg ))
160+ }
190161 }
191162
192- fmt .Fprintf (io .Out , "Postgres cluster %s is ready and attached to %s\n " , response .Data .Id , state .Plan .AppName )
193- fmt .Fprintf (io .Out , "The following secret was added to %s:\n DATABASE_URL=%s\n " , state .Plan .AppName , cluster .Credentials .ConnectionUri )
194-
195163 return nil
196164}
197165
0 commit comments