Skip to content

Commit

Permalink
Merge pull request #54 from zeabur/xiayuhang1106/zea-2180-check-user-…
Browse files Browse the repository at this point in the history
…login-status-when-use-zeabur-command

feat: use deploy as a default command if no command is specified
  • Loading branch information
MichaelYuhe authored Dec 20, 2023
2 parents 1ff4f71 + 5202de6 commit 93eadae
Show file tree
Hide file tree
Showing 10 changed files with 298 additions and 68 deletions.
3 changes: 1 addition & 2 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ linters:

linters-settings:
govet:
check-shadowing: true
enable-all: true
check-shadowing: false
disable: ["fieldalignment"]
errcheck:
exclude-functions:
8 changes: 7 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
package main

import (
"os"
"time"

"github.com/zeabur/cli/internal/cmd/root"
"github.com/zeabur/cli/internal/cmdutil"
"github.com/zeabur/cli/pkg/auth"
"github.com/zeabur/cli/pkg/config"
"github.com/zeabur/cli/pkg/log"
"github.com/zeabur/cli/pkg/printer"
"github.com/zeabur/cli/pkg/prompt"
"time"
)

var (
Expand All @@ -26,6 +28,10 @@ func main() {
panic(err)
}

if len(os.Args) <= 1 {
os.Args = append([]string{os.Args[0], "deploy"}, os.Args[1:]...)
}

// log errors
if err := rootCmd.Execute(); err != nil {
// when some errors occur(such as args dis-match), the log may not be initialized
Expand Down
106 changes: 45 additions & 61 deletions internal/cmd/deploy/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package deploy

import (
"context"
"fmt"

"github.com/briandowns/spinner"
"github.com/spf13/cobra"
"github.com/zeabur/cli/internal/cmdutil"
"github.com/zeabur/cli/internal/util"
"golang.org/x/sync/errgroup"
"github.com/zeabur/cli/pkg/zcontext"
)

type Options struct {
Expand All @@ -19,7 +20,7 @@ func NewCmdDeploy(f *cmdutil.Factory) *cobra.Command {

cmd := &cobra.Command{
Use: "deploy",
Short: "Deploy a local Git Service",
Short: "Deploy local project to Zeabur with one command",
PreRunE: util.NeedProjectContextWhenNonInteractive(f),
RunE: func(cmd *cobra.Command, args []string) error {
return runDeploy(f, opts)
Expand All @@ -32,97 +33,80 @@ func NewCmdDeploy(f *cmdutil.Factory) *cobra.Command {
}

func runDeploy(f *cmdutil.Factory, opts *Options) error {
if f.Interactive {
return runDeployInteractive(f, opts)
} else {
return runDeployNonInteractive(f, opts)
}
}

func runDeployNonInteractive(f *cmdutil.Factory, opts *Options) error {
repoOwner, repoName, err := f.ApiClient.GetRepoInfo()
s := spinner.New(cmdutil.SpinnerCharSet, cmdutil.SpinnerInterval,
spinner.WithColor(cmdutil.SpinnerColor),
spinner.WithSuffix(" Fetching projects ..."),
)
s.Start()
projects, err := f.ApiClient.ListAllProjects(context.Background())
if err != nil {
return err
}
s.Stop()

if len(projects) == 0 {
confirm, err := f.Prompter.Confirm("No projects found, would you like to create one now?", true)
if err != nil {
return err
}
if confirm {
project, err := f.ApiClient.CreateProject(context.Background(), "default", nil)
if err != nil {
f.Log.Error("Failed to create project: ", err)
return err
}
f.Log.Infof("Project %s created", project.Name)
f.Config.GetContext().SetProject(zcontext.NewBasicInfo(project.ID, project.Name))

return nil
}
}

f.Log.Info("Select one project to deploy your service.")

repoID, err := f.ApiClient.GetRepoID(repoOwner, repoName)
_, project, err := f.Selector.SelectProject()
if err != nil {
return err
}

f.Log.Debugf("repoID: %d", repoID)
f.Config.GetContext().SetProject(zcontext.NewBasicInfo(project.ID, project.Name))

//TODO: Deploy Local Git Service NonInteractive

return nil
}
_, environment, err := f.Selector.SelectEnvironment(project.ID)
if err != nil {
return err
}

func runDeployInteractive(f *cmdutil.Factory, opts *Options) error {
s := spinner.New(cmdutil.SpinnerCharSet, cmdutil.SpinnerInterval,
s = spinner.New(cmdutil.SpinnerCharSet, cmdutil.SpinnerInterval,
spinner.WithColor(cmdutil.SpinnerColor),
spinner.WithSuffix(" Fetching repository information..."),
spinner.WithSuffix(" Creating new service ..."),
)
s.Start()

var repoOwner string
var repoName string
var err error

repoOwner, repoName, err = f.ApiClient.GetRepoInfo()
bytes, fileName, err := util.PackZip()
if err != nil {
return err
}

// Use repo name as default service name
if opts.name == "" {
opts.name = repoName
}

var eg errgroup.Group
var repoID int
var branches []string

eg.Go(func() error {
repoID, err = f.ApiClient.GetRepoID(repoOwner, repoName)
return err
})

eg.Go(func() error {
branches, err = f.ApiClient.GetRepoBranches(context.Background(), repoOwner, repoName)
return err
})

if err = eg.Wait(); err != nil {
service, err := f.ApiClient.CreateEmptyService(context.Background(), project.ID, fileName)
if err != nil {
return err
}

s.Stop()

// If repo has only one branch, use it as default branch
// Otherwise, ask user to select a branch
var branch string

if len(branches) == 1 {
branch = branches[0]
} else {
_, err = f.Prompter.Select("Select branch", branch, branches)
if err != nil {
return err
}
}

s = spinner.New(cmdutil.SpinnerCharSet, cmdutil.SpinnerInterval,
spinner.WithColor(cmdutil.SpinnerColor),
spinner.WithSuffix(" Creating service..."),
spinner.WithFinalMSG(cmdutil.SuccessIcon+" Service created 🥂\n"),
spinner.WithSuffix(" Uploading codes to Zeabur ..."),
)
s.Start()

_, err = f.ApiClient.CreateService(context.Background(), f.Config.GetContext().GetProject().GetID(), opts.name, repoID, branch)
_, err = f.ApiClient.UploadZipToService(context.Background(), project.ID, service.ID, environment.ID, bytes)
if err != nil {
return err
}
s.Stop()

fmt.Println("Service created successfully, you can access it at: ", "https://dash.zeabur.com/projects/"+project.ID+"/services/"+service.ID+"?environmentID="+environment.ID)

return nil
}
3 changes: 3 additions & 0 deletions internal/cmd/project/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package create
import (
"context"
"fmt"

"github.com/zeabur/cli/pkg/zcontext"

"github.com/briandowns/spinner"
Expand Down Expand Up @@ -55,6 +56,8 @@ func runCreateInteractive(f *cmdutil.Factory, opts *Options) error {
if err != nil {
return err
}

regions = regions[1:]
s.Stop()

regionIDs := make([]string, 0, len(regions))
Expand Down
22 changes: 18 additions & 4 deletions internal/cmd/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
package root

import (
"errors"
"fmt"
"time"

"github.com/zeabur/cli/pkg/fill"
"github.com/zeabur/cli/pkg/selector"
"golang.org/x/oauth2"

"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
Expand All @@ -31,7 +31,7 @@ import (
// NewCmdRoot creates the root command
func NewCmdRoot(f *cmdutil.Factory, version, commit, date string) (*cobra.Command, error) {
cmd := &cobra.Command{
Use: "zeabur <command> <subcommand> [flags]",
Use: "zeabur",
Short: "Zeabur CLI",
Long: `Zeabur CLI is the official command line tool for Zeabur.`,
Example: heredoc.Doc(`
Expand All @@ -49,9 +49,23 @@ func NewCmdRoot(f *cmdutil.Factory, version, commit, date string) (*cobra.Comman

// require that the user is authenticated before running most commands
if cmdutil.IsAuthCheckEnabled(cmd) {
f.Log.Debug("Checking authentication")
// do not return error, guide user to login instead
if !f.LoggedIn() {
return errors.New("not authenticated")
f.Log.Info("A browser window will be opened for you to login, please confirm")

var (
tokenString string
token *oauth2.Token
err error
)

token, err = f.AuthClient.Login()
if err != nil {
return fmt.Errorf("failed to login: %w", err)
}
tokenString = token.AccessToken
f.Config.SetToken(token)
f.Config.SetTokenString(tokenString)
}
// set up the client
f.ApiClient = api.New(f.Config.GetTokenString())
Expand Down
113 changes: 113 additions & 0 deletions internal/util/pack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package util

import (
"archive/zip"
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
)

func PackZip() ([]byte, string, error) {
zipBytes, err := wrapNodeFunction(os.Getenv("PWD"), map[string]string{})

// turn pwd to a valid file name
fileName := filepath.Base(os.Getenv("PWD"))

if err != nil {
return nil, "", fmt.Errorf("wrap node function: %w", err)
}

return zipBytes, fileName, nil
}

func wrapNodeFunction(baseFolder string, envVars map[string]string) ([]byte, error) {
buf := new(bytes.Buffer)
w := zip.NewWriter(buf)

err := filepath.Walk(baseFolder, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("walking to %s: %w", path, err)
}

if info.IsDir() {
return nil
}

// This will ensure only the content inside baseFolder is included at the root of the ZIP.
relativePath, err := filepath.Rel(baseFolder, path)
if err != nil {
return fmt.Errorf("getting relative path: %w", err)
}

lstat, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("lstat: %w", err)
}

if lstat.Mode()&os.ModeSymlink == os.ModeSymlink {

zipFile, err := w.Create(relativePath + ".link")
if err != nil {
return fmt.Errorf("creating zip file: %w", err)
}

target, err := os.Readlink(path)
if err != nil {
return fmt.Errorf("read symlink: %w", err)
}

_, err = zipFile.Write([]byte(target))
if err != nil {
return fmt.Errorf("writing zip file: %w", err)
}

} else {

zipFile, err := w.Create(relativePath)
if err != nil {
return fmt.Errorf("creating zip file: %w", err)
}

fileContent, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("reading file: %w", err)
}

_, err = zipFile.Write(fileContent)
if err != nil {
return fmt.Errorf("writing zip file: %w", err)
}

}

zipFile, err := w.Create(".zeabur-env.json")
if err != nil {
return fmt.Errorf("creating zip file: %w", err)
}

envJsonStr, err := json.Marshal(envVars)
if err != nil {
return fmt.Errorf("marshaling env vars: %w", err)
}

_, err = zipFile.Write(envJsonStr)
if err != nil {
return fmt.Errorf("writing zip file: %w", err)
}

return nil
})

if err != nil {
return nil, fmt.Errorf("walking function directory: %w", err)
}

err = w.Close()
if err != nil {
return nil, fmt.Errorf("closing zip writer: %w", err)
}

return buf.Bytes(), nil
}
1 change: 1 addition & 0 deletions pkg/api/deploy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package api
2 changes: 2 additions & 0 deletions pkg/api/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ type (
ExposeService(ctx context.Context, id string, environmentID string, projectID string, name string) (*model.TempTCPPort, error)
CreateServiceFromMarketplace(ctx context.Context, projectID string, name string, itemCode string) (*model.Service, error)
CreateService(ctx context.Context, projectID string, name string, repoID int, branchName string) (*model.Service, error)
CreateEmptyService(ctx context.Context, projectID string, name string) (*model.Service, error)
UploadZipToService(ctx context.Context, projectID string, serviceID string, environmentID string, zipBytes []byte) (*model.Service, error)
}

DeploymentAPI interface {
Expand Down
Loading

0 comments on commit 93eadae

Please sign in to comment.