Skip to content

Commit

Permalink
Merge pull request #62 from DireLines/main
Browse files Browse the repository at this point in the history
feat: runpodctl project build
  • Loading branch information
DireLines authored Jan 10, 2024
2 parents 071b0bd + a6e2d43 commit 6aac165
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 28 deletions.
2 changes: 1 addition & 1 deletion cmd/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ func init() {
projectCmd.AddCommand(project.NewProjectCmd)
projectCmd.AddCommand(project.StartProjectCmd)
projectCmd.AddCommand(project.DeployProjectCmd)
// projectCmd.AddCommand(project.BuildProjectCmd)
projectCmd.AddCommand(project.BuildProjectCmd)
}
28 changes: 28 additions & 0 deletions cmd/project/exampleDockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# AUTOGENERATED Dockerfile using runpodctl project build

# Base image -> https://github.com/runpod/containers/blob/main/official-templates/base/Dockerfile
# DockerHub -> https://hub.docker.com/r/runpod/base/tags
FROM <<BASE_IMAGE>>

# The base image comes with many system dependencies pre-installed to help you get started quickly.
# Please refer to the base image's Dockerfile for more information before adding additional dependencies.
# IMPORTANT: The base image overrides the default huggingface cache location.

# System dependencies
# if you need additional system dependencies beyond those included in the base image, feel free to add them here
# but be aware those changes will not be reflected in the development pod
# unless you override the base image in runpod.toml to an image that includes them.

# Python dependencies
COPY <<REQUIREMENTS_PATH>> /requirements.txt
RUN python<<PYTHON_VERSION>> -m pip install --upgrade pip && \
python<<PYTHON_VERSION>> -m pip install --upgrade -r /requirements.txt --no-cache-dir && \
rm /requirements.txt

# NOTE: The base image comes with multiple Python versions pre-installed.
# It is recommended to specify the version of Python when running your code.

# Add src files (Worker Template)
ADD src .
<<SET_ENV_VARS>>
CMD python<<PYTHON_VERSION>> -u <<HANDLER_PATH>>
43 changes: 41 additions & 2 deletions cmd/project/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@ var starterTemplates embed.FS
//go:embed example.toml
var tomlTemplate embed.FS

//go:embed exampleDockerfile
var dockerfileTemplate embed.FS

const basePath string = "starter_templates"

func baseDockerImage(cudaVersion string) string {
return fmt.Sprintf("runpod/base:0.4.4-cuda%s", cudaVersion)
}

func copyFiles(files fs.FS, source string, dest string) error {
return fs.WalkDir(starterTemplates, source, func(path string, d fs.DirEntry, err error) error {
return fs.WalkDir(files, source, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
Expand All @@ -43,7 +46,7 @@ func copyFiles(files fs.FS, source string, dest string) error {
if d.IsDir() {
return os.MkdirAll(newPath, os.ModePerm)
} else {
content, err := fs.ReadFile(starterTemplates, path)
content, err := fs.ReadFile(files, path)
if err != nil {
return err
}
Expand Down Expand Up @@ -212,6 +215,13 @@ func mapToApiEnv(env map[string]string) []*api.PodEnv {
}
return podEnv
}
func formatAsDockerEnv(env map[string]string) string {
result := ""
for k, v := range env {
result += fmt.Sprintf("ENV %s=%s\n", k, v)
}
return result
}

func startProject(networkVolumeId string) error {
//parse project toml
Expand Down Expand Up @@ -475,3 +485,32 @@ func deployProject(networkVolumeId string) (endpointId string, err error) {
}
return deployedEndpointId, nil
}

func buildProjectDockerfile() {
//parse project toml
config := loadProjectConfig()
projectConfig := config.Get("project").(*toml.Tree)
runtimeConfig := config.Get("runtime").(*toml.Tree)
//build Dockerfile
dockerfileBytes, _ := dockerfileTemplate.ReadFile("exampleDockerfile")
dockerfile := string(dockerfileBytes)
//base image: from toml
dockerfile = strings.ReplaceAll(dockerfile, "<<BASE_IMAGE>>", projectConfig.Get("base_image").(string))
//pip requirements
dockerfile = strings.ReplaceAll(dockerfile, "<<REQUIREMENTS_PATH>>", runtimeConfig.Get("requirements_path").(string))
dockerfile = strings.ReplaceAll(dockerfile, "<<PYTHON_VERSION>>", runtimeConfig.Get("python_version").(string))
//cmd: start handler
dockerfile = strings.ReplaceAll(dockerfile, "<<HANDLER_PATH>>", runtimeConfig.Get("handler_path").(string))
if includeEnvInDockerfile {
dockerEnv := formatAsDockerEnv(createEnvVars(config))
dockerfile = strings.ReplaceAll(dockerfile, "<<SET_ENV_VARS>>", "\n"+dockerEnv)
} else {
dockerfile = strings.ReplaceAll(dockerfile, "<<SET_ENV_VARS>>", "")
}
//save to Dockerfile in project directory
projectFolder, _ := os.Getwd()
dockerfilePath := filepath.Join(projectFolder, "Dockerfile")
os.WriteFile(dockerfilePath, []byte(dockerfile), 0644)
fmt.Printf("Dockerfile created at %s\n", dockerfilePath)

}
107 changes: 83 additions & 24 deletions cmd/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var modelType string
var modelName string
var initCurrentDir bool
var setDefaultNetworkVolume bool
var includeEnvInDockerfile bool

const inputPromptPrefix string = " > "

Expand Down Expand Up @@ -46,10 +47,6 @@ func promptChoice(message string, choices []string, defaultChoice string) string
}
return s
}
func setDefaultNetVolume(projectId string, networkVolumeId string) {
viper.Set(fmt.Sprintf("project_volumes.%s", projectId), networkVolumeId)
viper.WriteConfig()
}

func selectNetworkVolume() (networkVolumeId string, err error) {
networkVolumes, err := api.GetNetworkVolumes()
Expand Down Expand Up @@ -86,6 +83,40 @@ func selectNetworkVolume() (networkVolumeId string, err error) {
networkVolumeId = options[i].Value
return networkVolumeId, nil
}
func selectStarterTemplate() (template string, err error) {
type StarterTemplateOption struct {
Name string // The string to display
Value string // The actual value to use
}
templates, err := starterTemplates.ReadDir("starter_templates")
if err != nil {
fmt.Println("Something went wrong trying to fetch starter templates")
fmt.Println(err)
return "", err
}
promptTemplates := &promptui.SelectTemplates{
Label: inputPromptPrefix + "{{ . }}",
Active: ` {{ "●" | cyan }} {{ .Name | cyan }}`,
Inactive: ` {{ .Name | white }}`,
Selected: ` {{ .Name | white }}`,
}
options := []StarterTemplateOption{}
for _, template := range templates {
options = append(options, StarterTemplateOption{Name: template.Name(), Value: template.Name()})
}
getStarterTemplate := promptui.Select{
Label: "Select a Starter Template:",
Items: options,
Templates: promptTemplates,
}
i, _, err := getStarterTemplate.Run()
if err != nil {
//ctrl c for example
return "", err
}
template = options[i].Value
return template, nil
}

// Define a struct that holds the display string and the corresponding value
type NetVolOption struct {
Expand All @@ -105,17 +136,24 @@ var NewProjectCmd = &cobra.Command{
} else {
fmt.Println("Project name: " + projectName)
}
if modelType == "" {
template, err := selectStarterTemplate()
modelType = template
if err != nil {
modelType = ""
}
}
cudaVersion := promptChoice("Select a CUDA version, or press enter to use the default",
[]string{"11.1.1", "11.8.0", "12.1.0"}, "11.8.0")
pythonVersion := promptChoice("Select a Python version, or press enter to use the default",
[]string{"3.8", "3.9", "3.10", "3.11"}, "3.10")

fmt.Printf(`
Project Summary:
- Project Name: %s
- Starter Template: %s
- CUDA Version: %s
- Python Version: %s
`, projectName, cudaVersion, pythonVersion)
`, projectName, modelType, cudaVersion, pythonVersion)
fmt.Println()
fmt.Println("The project will be created in the current directory.")
//TODO confirm y/n
Expand All @@ -136,7 +174,16 @@ var StartProjectCmd = &cobra.Command{
config := loadProjectConfig()
projectId := config.GetPath([]string{"project", "uuid"}).(string)
networkVolumeId := viper.GetString(fmt.Sprintf("project_volumes.%s", projectId))
if setDefaultNetworkVolume || networkVolumeId == "" {
cachedNetVolExists := false
networkVolumes, err := api.GetNetworkVolumes()
if err == nil {
for _, networkVolume := range networkVolumes {
if networkVolume.Id == networkVolumeId {
cachedNetVolExists = true
}
}
}
if setDefaultNetworkVolume || networkVolumeId == "" || !cachedNetVolExists {
netVolId, err := selectNetworkVolume()
networkVolumeId = netVolId
viper.Set(fmt.Sprintf("project_volumes.%s", projectId), networkVolumeId)
Expand Down Expand Up @@ -175,22 +222,33 @@ var DeployProjectCmd = &cobra.Command{
},
}

// var BuildProjectCmd = &cobra.Command{
// Use: "build",
// Args: cobra.ExactArgs(0),
// Short: "build Docker image for current project",
// Long: "build a Docker image for the Runpod project in the current folder",
// Run: func(cmd *cobra.Command, args []string) {
// //parse project toml
// //build Dockerfile
// //base image: from toml
// //run setup.sh for system deps
// //pip install requirements
// //cmd: start handler
// //docker build
// //print next steps
// },
// }
var BuildProjectCmd = &cobra.Command{
Use: "build",
Args: cobra.ExactArgs(0),
Short: "build Dockerfile for current project",
Long: "build a Dockerfile for the Runpod project in the current folder",
Run: func(cmd *cobra.Command, args []string) {
buildProjectDockerfile()
// config := loadProjectConfig()
// projectConfig := config.Get("project").(*toml.Tree)
// projectId := projectConfig.Get("uuid").(string)
// projectName := projectConfig.Get("name").(string)
// //print next steps
// fmt.Println("Next steps:")
// fmt.Println()
// suggestedDockerTag := fmt.Sprintf("runpod-sls-worker-%s-%s:0.1", projectName, projectId)
// //docker build
// fmt.Println("# Build Docker image")
// fmt.Printf("docker build -t %s .\n", suggestedDockerTag)
// //dockerhub push
// fmt.Println("# Push Docker image to a container registry such as Dockerhub")
// fmt.Printf("docker push %s\n", suggestedDockerTag)
// //go to runpod url and deploy
// fmt.Println()
// fmt.Println("Deploy docker image as a serverless endpoint on Runpod")
// fmt.Println("https://www.runpod.io/console/serverless")
},
}

func init() {
NewProjectCmd.Flags().StringVarP(&projectName, "name", "n", "", "project name")
Expand All @@ -199,5 +257,6 @@ func init() {
NewProjectCmd.Flags().BoolVarP(&initCurrentDir, "init", "i", false, "use the current directory as the project directory")

StartProjectCmd.Flags().BoolVar(&setDefaultNetworkVolume, "select-volume", false, "select a new default network volume for current project")
DeployProjectCmd.Flags().BoolVar(&setDefaultNetworkVolume, "select-volume", false, "select a new default network volume for current project")
BuildProjectCmd.Flags().BoolVar(&includeEnvInDockerfile, "include-env", false, "include environment variables from runpod.toml in generated Dockerfile")

}
1 change: 1 addition & 0 deletions cmd/project/starter_templates/default/.runpodignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Similar to .gitignore
# Matches will not be synce to the development pod or cause the development pod to reload.

Dockerfile
1 change: 1 addition & 0 deletions cmd/project/starter_templates/llama2/.runpodignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Similar to .gitignore
# Matches will not be synce to the development pod or cause the development pod to reload.

Dockerfile
2 changes: 1 addition & 1 deletion makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: proto

dev:
env GOOS=windows GOARCH=amd64 go build -ldflags "-X 'main.Version=1.0.0'" -o bin/runpodctl.exe .
env GOOS=darwin GOARCH=arm64 go build -ldflags "-X 'main.Version=1.0.0'" -o bin/runpodctl .
lint:
golangci-lint run

0 comments on commit 6aac165

Please sign in to comment.