From 87074709c78c0bb7325d710bf2d69235a5c4f889 Mon Sep 17 00:00:00 2001 From: phani Date: Mon, 29 Sep 2025 14:18:08 -0700 Subject: [PATCH 1/4] github url public repo deploy works --- cmd/deploy.go | 182 ++++++++++++++++++++++++++++++- cmd/invoke.go | 2 +- go.mod | 2 + go.sum | 6 +- scripts/go-mod-replace-kernel.sh | 12 +- 5 files changed, 192 insertions(+), 12 deletions(-) diff --git a/cmd/deploy.go b/cmd/deploy.go index fae6b68..01ffd03 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -1,7 +1,13 @@ package cmd import ( + "bytes" + "encoding/json" "fmt" + "io" + "mime/multipart" + "net/http" + "net/textproto" "os" "path/filepath" "strings" @@ -36,6 +42,14 @@ var deployCmd = &cobra.Command{ RunE: runDeploy, } +// deployGithubCmd deploys directly from a GitHub repository via the SDK Source flow +var deployGithubCmd = &cobra.Command{ + Use: "github", + Short: "Deploy from a GitHub repository", + Args: cobra.NoArgs, + RunE: runDeployGithub, +} + func init() { deployCmd.Flags().String("version", "latest", "Specify a version for the app (default: latest)") deployCmd.Flags().Bool("force", false, "Allow overwrite of an existing version with the same name") @@ -50,6 +64,172 @@ func init() { deployHistoryCmd.Flags().Int("limit", 100, "Max deployments to return (default 100)") deployCmd.AddCommand(deployHistoryCmd) + + // Flags for GitHub deploy + deployGithubCmd.Flags().String("url", "", "GitHub repository URL (e.g., https://github.com/org/repo)") + deployGithubCmd.Flags().String("ref", "", "Git ref to deploy (branch, tag, or commit SHA)") + deployGithubCmd.Flags().String("entrypoint", "", "Entrypoint within the repo/path (e.g., src/index.ts)") + deployGithubCmd.Flags().String("path", "", "Optional subdirectory within the repo (e.g., apps/api)") + _ = deployGithubCmd.MarkFlagRequired("url") + _ = deployGithubCmd.MarkFlagRequired("ref") + _ = deployGithubCmd.MarkFlagRequired("entrypoint") + deployCmd.AddCommand(deployGithubCmd) +} + +func runDeployGithub(cmd *cobra.Command, args []string) error { + client := getKernelClient(cmd) + + repoURL, _ := cmd.Flags().GetString("url") + ref, _ := cmd.Flags().GetString("ref") + entrypoint, _ := cmd.Flags().GetString("entrypoint") + subpath, _ := cmd.Flags().GetString("path") + + version, _ := cmd.Flags().GetString("version") + force, _ := cmd.Flags().GetBool("force") + + // Collect env vars similar to runDeploy + envPairs, _ := cmd.Flags().GetStringArray("env") + envFiles, _ := cmd.Flags().GetStringArray("env-file") + + envVars := make(map[string]string) + // Load from files first + for _, envFile := range envFiles { + fileVars, err := godotenv.Read(envFile) + if err != nil { + return fmt.Errorf("failed to read env file %s: %w", envFile, err) + } + for k, v := range fileVars { + envVars[k] = v + } + } + // Override with --env + for _, kv := range envPairs { + parts := strings.SplitN(kv, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("invalid env variable format: %s (expected KEY=value)", kv) + } + envVars[parts[0]] = parts[1] + } + + // Build the multipart request body directly for source-based deploy + + pterm.Info.Println("Deploying from GitHub source...") + startTime := time.Now() + + // Manually POST multipart with a JSON 'source' field to match backend expectations + apiKey := os.Getenv("KERNEL_API_KEY") + if strings.TrimSpace(apiKey) == "" { + return fmt.Errorf("KERNEL_API_KEY is required for github deploy") + } + baseURL := os.Getenv("KERNEL_BASE_URL") + if strings.TrimSpace(baseURL) == "" { + baseURL = "https://api.onkernel.com" + } + + var body bytes.Buffer + mw := multipart.NewWriter(&body) + // regular fields + _ = mw.WriteField("version", version) + _ = mw.WriteField("region", "aws.us-east-1a") + if force { + _ = mw.WriteField("force", "true") + } else { + _ = mw.WriteField("force", "false") + } + // env vars as env_vars[KEY] + for k, v := range envVars { + _ = mw.WriteField(fmt.Sprintf("env_vars[%s]", k), v) + } + // source as application/json part + sourcePayload := map[string]any{ + "type": "github", + "url": repoURL, + "ref": ref, + "entrypoint": entrypoint, + } + if strings.TrimSpace(subpath) != "" { + sourcePayload["path"] = subpath + } + srcJSON, _ := json.Marshal(sourcePayload) + hdr := textproto.MIMEHeader{} + hdr.Set("Content-Disposition", "form-data; name=\"source\"") + hdr.Set("Content-Type", "application/json") + part, _ := mw.CreatePart(hdr) + _, _ = part.Write(srcJSON) + _ = mw.Close() + + reqHTTP, _ := http.NewRequestWithContext(cmd.Context(), http.MethodPost, strings.TrimRight(baseURL, "/")+"/deployments", &body) + reqHTTP.Header.Set("Authorization", "Bearer "+apiKey) + reqHTTP.Header.Set("Content-Type", mw.FormDataContentType()) + httpResp, err := http.DefaultClient.Do(reqHTTP) + if err != nil { + return fmt.Errorf("post deployments: %w", err) + } + defer httpResp.Body.Close() + if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 { + b, _ := io.ReadAll(httpResp.Body) + return fmt.Errorf("deployments POST failed: %s: %s", httpResp.Status, strings.TrimSpace(string(b))) + } + var depCreated struct { + ID string `json:"id"` + } + if err := json.NewDecoder(httpResp.Body).Decode(&depCreated); err != nil { + return fmt.Errorf("decode deployment response: %w", err) + } + + // Follow deployment events via SSE using the same base URL and API key + stream := client.Deployments.FollowStreaming( + cmd.Context(), + depCreated.ID, + kernel.DeploymentFollowParams{}, + option.WithBaseURL(baseURL), + option.WithHeader("Authorization", "Bearer "+apiKey), + option.WithMaxRetries(0), + ) + for stream.Next() { + data := stream.Current() + switch data.Event { + case "log": + logEv := data.AsLog() + msg := strings.TrimSuffix(logEv.Message, "\n") + pterm.Info.Println(pterm.Gray(msg)) + case "deployment_state": + deploymentState := data.AsDeploymentState() + status := deploymentState.Deployment.Status + if status == string(kernel.DeploymentGetResponseStatusFailed) || + status == string(kernel.DeploymentGetResponseStatusStopped) { + pterm.Error.Println("✖ Deployment failed") + pterm.Error.Printf("Deployment ID: %s\n", depCreated.ID) + pterm.Info.Printf("View logs: kernel deploy logs %s --since 1h\n", depCreated.ID) + return fmt.Errorf("deployment %s: %s", status, deploymentState.Deployment.StatusReason) + } + if status == string(kernel.DeploymentGetResponseStatusRunning) { + duration := time.Since(startTime) + pterm.Success.Printfln("✔ Deployment complete in %s", duration.Round(time.Millisecond)) + return nil + } + case "app_version_summary": + appVersionSummary := data.AsDeploymentFollowResponseAppVersionSummaryEvent() + pterm.Info.Printf("App \"%s\" deployed (version: %s)\n", appVersionSummary.AppName, appVersionSummary.Version) + if len(appVersionSummary.Actions) > 0 { + action0Name := appVersionSummary.Actions[0].Name + pterm.Info.Printf("Invoke with: kernel invoke %s %s --payload '{...}'\n", quoteIfNeeded(appVersionSummary.AppName), quoteIfNeeded(action0Name)) + } + case "error": + errorEv := data.AsErrorEvent() + pterm.Error.Printf("Deployment ID: %s\n", depCreated.ID) + pterm.Info.Printf("View logs: kernel deploy logs %s --since 1h\n", depCreated.ID) + return fmt.Errorf("%s: %s", errorEv.Error.Code, errorEv.Error.Message) + } + } + + if serr := stream.Err(); serr != nil { + pterm.Error.Println("✖ Stream error") + pterm.Error.Printf("Deployment ID: %s\n", depCreated.ID) + pterm.Info.Printf("View logs: kernel deploy logs %s --since 1h\n", depCreated.ID) + return fmt.Errorf("stream error: %w", serr) + } + return nil } func runDeploy(cmd *cobra.Command, args []string) (err error) { @@ -120,7 +300,7 @@ func runDeploy(cmd *cobra.Command, args []string) (err error) { File: file, Version: kernel.Opt(version), Force: kernel.Opt(force), - EntrypointRelPath: filepath.Base(resolvedEntrypoint), + EntrypointRelPath: kernel.Opt(filepath.Base(resolvedEntrypoint)), EnvVars: envVars, }, option.WithMaxRetries(0)) if err != nil { diff --git a/cmd/invoke.go b/cmd/invoke.go index 0fa4438..25c871a 100644 --- a/cmd/invoke.go +++ b/cmd/invoke.go @@ -107,7 +107,7 @@ func runInvoke(cmd *cobra.Command, args []string) error { }) // Start following events - stream := client.Invocations.FollowStreaming(cmd.Context(), resp.ID, option.WithMaxRetries(0)) + stream := client.Invocations.FollowStreaming(cmd.Context(), resp.ID, kernel.InvocationFollowParams{}, option.WithMaxRetries(0)) for stream.Next() { ev := stream.Current() diff --git a/go.mod b/go.mod index 3032472..10bd793 100644 --- a/go.mod +++ b/go.mod @@ -58,3 +58,5 @@ require ( golang.org/x/text v0.24.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/onkernel/kernel-go-sdk => github.com/stainless-sdks/kernel-go v0.0.0-20250925165623-ec51df239db4 diff --git a/go.sum b/go.sum index f9360bc..6d2d848 100644 --- a/go.sum +++ b/go.sum @@ -91,10 +91,6 @@ github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0= github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= -github.com/onkernel/kernel-go-sdk v0.11.0 h1:7KUKHiz5t4jdnNCwA8NM1dTtEYdk/AV/RIe8T/HjJwg= -github.com/onkernel/kernel-go-sdk v0.11.0/go.mod h1:q7wsAf+yjpY+w8jbAMciWCtCM0ZUxiw/5o2MSPTZS9E= -github.com/onkernel/kernel-go-sdk v0.11.1 h1:gTxhXtsXrJcrM7KEobEVXa8mPPtRFMlxQwNqkyoCrDI= -github.com/onkernel/kernel-go-sdk v0.11.1/go.mod h1:q7wsAf+yjpY+w8jbAMciWCtCM0ZUxiw/5o2MSPTZS9E= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -120,6 +116,8 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stainless-sdks/kernel-go v0.0.0-20250925165623-ec51df239db4 h1:XxFDKXTl2Av2tJBRuaoIUUf8scWMYompJiYSbZnW5ho= +github.com/stainless-sdks/kernel-go v0.0.0-20250925165623-ec51df239db4/go.mod h1:MjUR92i8UPqjrmneyVykae6GuB3GGSmnQtnjf1v74Dc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= diff --git a/scripts/go-mod-replace-kernel.sh b/scripts/go-mod-replace-kernel.sh index c671cdd..64351a4 100755 --- a/scripts/go-mod-replace-kernel.sh +++ b/scripts/go-mod-replace-kernel.sh @@ -12,12 +12,12 @@ fi # Ensure the user's git configuration rewrites GitHub HTTPS URLs to SSH. This is # required to clone private repositories via SSH without using a Github PAT. -if ! git config --global --get-all url."git@github.com:".insteadOf | grep -q "https://github.com/"; then - echo "Your git configuration is missing the rewrite from HTTPS to SSH for GitHub repositories." >&2 - echo "Run the following command and try again:" >&2 - echo " git config --global url.\"git@github.com:\".insteadOf \"https://github.com/\"" >&2 - exit 1 -fi +# if ! git config --global --get-all url."git@github.com:".insteadOf | grep -q "https://github.com/"; then +# echo "Your git configuration is missing the rewrite from HTTPS to SSH for GitHub repositories." >&2 +# echo "Run the following command and try again:" >&2 +# echo " git config --global url.\"git@github.com:\".insteadOf \"https://github.com/\"" >&2 +# exit 1 +# fi # Ensure exactly one ref (commit hash or branch name) is provided if [ "$#" -ne 1 ]; then From 17df432b9a64fde4651f6a8ecf3fa4db8ebeb67d Mon Sep 17 00:00:00 2001 From: phani Date: Tue, 30 Sep 2025 15:06:04 -0700 Subject: [PATCH 2/4] token flag added for the private repos --- cmd/deploy.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/deploy.go b/cmd/deploy.go index 01ffd03..defa540 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -70,6 +70,7 @@ func init() { deployGithubCmd.Flags().String("ref", "", "Git ref to deploy (branch, tag, or commit SHA)") deployGithubCmd.Flags().String("entrypoint", "", "Entrypoint within the repo/path (e.g., src/index.ts)") deployGithubCmd.Flags().String("path", "", "Optional subdirectory within the repo (e.g., apps/api)") + deployGithubCmd.Flags().String("github-token", "", "GitHub token for private repositories (PAT or installation access token)") _ = deployGithubCmd.MarkFlagRequired("url") _ = deployGithubCmd.MarkFlagRequired("ref") _ = deployGithubCmd.MarkFlagRequired("entrypoint") @@ -83,6 +84,7 @@ func runDeployGithub(cmd *cobra.Command, args []string) error { ref, _ := cmd.Flags().GetString("ref") entrypoint, _ := cmd.Flags().GetString("entrypoint") subpath, _ := cmd.Flags().GetString("path") + ghToken, _ := cmd.Flags().GetString("github-token") version, _ := cmd.Flags().GetString("version") force, _ := cmd.Flags().GetBool("force") @@ -150,6 +152,13 @@ func runDeployGithub(cmd *cobra.Command, args []string) error { if strings.TrimSpace(subpath) != "" { sourcePayload["path"] = subpath } + if strings.TrimSpace(ghToken) != "" { + // Add auth only when token is provided to support private repositories + sourcePayload["auth"] = map[string]any{ + "method": "github_token", + "token": ghToken, + } + } srcJSON, _ := json.Marshal(sourcePayload) hdr := textproto.MIMEHeader{} hdr.Set("Content-Disposition", "form-data; name=\"source\"") From 64cb2665a6807f6b00156d87cb4a7519df4c4aee Mon Sep 17 00:00:00 2001 From: phani Date: Tue, 30 Sep 2025 15:28:10 -0700 Subject: [PATCH 3/4] script change undo --- scripts/go-mod-replace-kernel.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/go-mod-replace-kernel.sh b/scripts/go-mod-replace-kernel.sh index 64351a4..c671cdd 100755 --- a/scripts/go-mod-replace-kernel.sh +++ b/scripts/go-mod-replace-kernel.sh @@ -12,12 +12,12 @@ fi # Ensure the user's git configuration rewrites GitHub HTTPS URLs to SSH. This is # required to clone private repositories via SSH without using a Github PAT. -# if ! git config --global --get-all url."git@github.com:".insteadOf | grep -q "https://github.com/"; then -# echo "Your git configuration is missing the rewrite from HTTPS to SSH for GitHub repositories." >&2 -# echo "Run the following command and try again:" >&2 -# echo " git config --global url.\"git@github.com:\".insteadOf \"https://github.com/\"" >&2 -# exit 1 -# fi +if ! git config --global --get-all url."git@github.com:".insteadOf | grep -q "https://github.com/"; then + echo "Your git configuration is missing the rewrite from HTTPS to SSH for GitHub repositories." >&2 + echo "Run the following command and try again:" >&2 + echo " git config --global url.\"git@github.com:\".insteadOf \"https://github.com/\"" >&2 + exit 1 +fi # Ensure exactly one ref (commit hash or branch name) is provided if [ "$#" -ne 1 ]; then From 64bc4d7ff483e5ba41b2e45f91f090e6ff9708fe Mon Sep 17 00:00:00 2001 From: phani Date: Tue, 30 Sep 2025 16:15:53 -0700 Subject: [PATCH 4/4] merge conflict/test fixes --- cmd/deploy.go | 2 +- go.mod | 1 - go.sum | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/cmd/deploy.go b/cmd/deploy.go index defa540..c115119 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -309,7 +309,7 @@ func runDeploy(cmd *cobra.Command, args []string) (err error) { File: file, Version: kernel.Opt(version), Force: kernel.Opt(force), - EntrypointRelPath: kernel.Opt(filepath.Base(resolvedEntrypoint)), + EntrypointRelPath: filepath.Base(resolvedEntrypoint), EnvVars: envVars, }, option.WithMaxRetries(0)) if err != nil { diff --git a/go.mod b/go.mod index 66549fc..8123057 100644 --- a/go.mod +++ b/go.mod @@ -59,4 +59,3 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -replace github.com/onkernel/kernel-go-sdk => github.com/stainless-sdks/kernel-go v0.0.0-20250925165623-ec51df239db4 diff --git a/go.sum b/go.sum index ae1c431..21539b0 100644 --- a/go.sum +++ b/go.sum @@ -118,8 +118,6 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stainless-sdks/kernel-go v0.0.0-20250925165623-ec51df239db4 h1:XxFDKXTl2Av2tJBRuaoIUUf8scWMYompJiYSbZnW5ho= -github.com/stainless-sdks/kernel-go v0.0.0-20250925165623-ec51df239db4/go.mod h1:MjUR92i8UPqjrmneyVykae6GuB3GGSmnQtnjf1v74Dc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=