1515package commands
1616
1717import (
18- "bytes"
1918 "context"
2019 "embed"
2120 "errors"
2221 "fmt"
2322 "os"
2423 "os/exec"
25- "strings"
2624
2725 "github.com/heroku/color"
2826 "github.com/spf13/cobra"
2927)
3028
3129//go:embed embed/Dockerfile
3230var f embed.FS
31+ var execCmdFn = execCmd
32+ var execLookPathFn = exec .LookPath
3333
3434const (
35- ImageTag = "function:latest"
35+ // Builder Type
36+ Ko = "ko"
37+ Docker = "docker"
38+
39+ // Docker constant variables
40+ Image = "function:latest"
3641 DockerfilePath = "Dockerfile"
37- builtinDockerfilePath = "embed/Dockerfile"
42+ BuiltinDockerfilePath = "embed/Dockerfile"
43+
44+ // Ko constant variables
45+ KoDockerRepoEnvVar = "KO_DOCKER_REPO"
46+ KoLocalRepo = "ko.local"
3847)
3948
40- func NewBuildCmd (ctx context.Context ) * cobra. Command {
49+ func NewBuildRunner (ctx context.Context ) * BuildRunner {
4150 r := & BuildRunner {
42- ctx : ctx ,
51+ ctx : ctx ,
52+ Ko : & KoBuilder {},
53+ Docker : & DockerBuilder {},
4354 }
4455 r .Command = & cobra.Command {
45- Use : "build" ,
46- Short : "build the KRM function as a docker image" ,
47- PreRunE : r .PreRunE ,
48- RunE : r .RunE ,
49- }
50- r .Command .Flags ().StringVarP (& r .Tag , "tag" , "t" , ImageTag ,
51- "the docker image tag" )
52- r .Command .Flags ().StringVarP (& r .DockerfilePath , "file" , "f" , "" ,
53- "Name of the Dockerfile. If not given, using a default builtin Dockerfile" )
54- return r .Command
56+ Use : "build" ,
57+ Short : "build your KRM function to a container image" ,
58+ RunE : r .RunE ,
59+ }
60+ r .Command .Flags ().StringVarP (& r .BuilderType , "builder" , "b" , Ko ,
61+ "the image builder. `ko` is the default builder, which requires `go build`; `docker` is accepted, and " +
62+ " requires you to have docker installed and running" )
63+ r .Command .Flags ().StringVarP (& r .Docker .Image , "image" , "i" , Image ,
64+ fmt .Sprintf ("the image (with tag), default to %v" , Image ))
65+ r .Command .Flags ().StringVarP (& r .Docker .DockerfilePath , "dockerfile" , "f" , "" ,
66+ "path to the Dockerfile. If not given, using a default builtin Dockerfile" )
67+ r .Command .Flags ().StringVarP (& r .Ko .Repo , "repo" , "r" , "" ,
68+ "the image repo. default to ko.local" )
69+ r .Command .Flags ().StringVarP (& r .Ko .Tag , "tag" , "t" , "latest" ,
70+ "the ko image tag" )
71+ // TODO: Docker CLI uses `--tag` flag to refer to "image:tag", which could be confusing but broadly accepted.
72+ // We should better guide users on how to use "tag" and "image" flags for kfn.
73+ // Here we use "tag" for ko <tag> (same as `ko build --tag`) and "image" for docker <image:tag> (same as `docker build --tag`)
74+ return r
5575}
5676
5777type BuildRunner struct {
5878 ctx context.Context
5979 Command * cobra.Command
6080
61- Tag string
81+ BuilderType string
82+ Tag string
83+ Ko * KoBuilder
84+ Docker * DockerBuilder
85+ }
86+
87+ type Builder interface {
88+ Build () error
89+ Validate () error
90+ }
91+
92+ type DockerBuilder struct {
93+ Image string
6294 DockerfilePath string
6395}
6496
65- func (r * BuildRunner ) PreRunE (cmd * cobra.Command , args []string ) error {
66- if err := r .requireDocker (); err != nil {
67- return err
68- }
69- if ! r .dockerfileExist () {
70- err := r .createDockerfile ()
71- if err != nil {
72- return err
73- }
74- }
75- return nil
97+ type KoBuilder struct {
98+ Repo string
99+ Tag string
76100}
77101
78102func (r * BuildRunner ) RunE (cmd * cobra.Command , args []string ) error {
79- return r .runDockerBuild ()
103+ var builder Builder
104+ switch r .BuilderType {
105+ case Docker :
106+ builder = r .Docker
107+ case Ko :
108+ builder = r .Ko
109+ }
110+ if err := builder .Validate (); err != nil {
111+ return err
112+ }
113+ return builder .Build ()
80114}
81115
82- func (r * BuildRunner ) runDockerBuild () error {
83- args := []string {"build" , "." , "-f" , r .DockerfilePath , "--tag" , r .Tag }
84- cmd := exec .Command ("docker" , args ... )
85- var out , errout bytes.Buffer
86- cmd .Stdout = & out
87- cmd .Stderr = & errout
88- err := cmd .Run ()
116+ func (r * DockerBuilder ) Build () error {
117+ args := []string {"build" , "." , "-f" , r .DockerfilePath , "--tag" , r .Image }
118+ err := execCmdFn (nil , "docker" , args ... )
89119 if err != nil {
90- color .Red (strings .TrimSpace (errout .String ()))
91120 return err
92121 }
93- color .Green (out .String ())
94- color .Green ("Image %v builds successfully. Now you can publish the image" , r .Tag )
122+ color .Green ("Image %v built successfully. Now you can publish the image" , r .Image )
95123 return nil
96124}
97125
98- func (r * BuildRunner ) requireDocker () error {
99- _ , err := exec .LookPath ("docker" )
126+ func (r * DockerBuilder ) Validate () error {
127+ if err := r .validateDockerInstalled (); err != nil {
128+ return err
129+ }
130+ if r .dockerfileExists () {
131+ return nil
132+ }
133+ return r .createDockerfile ()
134+ }
135+
136+ func (r * DockerBuilder ) validateDockerInstalled () error {
137+ _ , err := execLookPathFn ("docker" )
100138 if err != nil {
101139 return fmt .Errorf ("kfn requires that `docker` is installed and on the PATH" )
102140 }
103141 return nil
104142}
105143
106- func (r * BuildRunner ) dockerfileExist () bool {
144+ func (r * DockerBuilder ) dockerfileExists () bool {
107145 if r .DockerfilePath == "" {
108146 r .DockerfilePath = DockerfilePath
109147 }
@@ -114,14 +152,80 @@ func (r *BuildRunner) dockerfileExist() bool {
114152 return true
115153}
116154
117- func (r * BuildRunner ) createDockerfile () error {
118- dockerfileContent , err := f .ReadFile (builtinDockerfilePath )
155+ func (r * DockerBuilder ) createDockerfile () error {
156+ dockerfileContent , err := f .ReadFile (BuiltinDockerfilePath )
157+ if err != nil {
158+ return err
159+ }
160+ if err = os .WriteFile (DockerfilePath , dockerfileContent , 0644 ); err != nil {
161+ return err
162+ }
163+ fmt .Println ("created Dockerfile" )
164+ return nil
165+ }
166+
167+ func (r * KoBuilder ) GuaranteeKoInstalled () error {
168+ _ , err := execLookPathFn ("ko" )
169+ if err == nil {
170+ return nil
171+ }
172+ gobin := os .Getenv ("GOBIN" )
173+ if gobin == "" && os .Getenv ("GOPATH" ) != "" {
174+ gobin = os .Getenv ("GOPATH" ) + "/bin"
175+ }
176+ if gobin == "" && os .Getenv ("HOME" ) != "" {
177+ gobin = os .Getenv ("HOME" ) + "/go/bin"
178+ }
179+ var envs []string
180+ if gobin != "" {
181+ envs = []string {"GOBIN" + "=" + gobin }
182+ }
183+ if err = execCmdFn (envs , "go" , "install" , "github.com/google/ko@latest" ); err != nil {
184+ return err
185+ }
186+ fmt .Println ("successfully installed ko" )
187+ return nil
188+ }
189+ func (r * KoBuilder ) Build () error {
190+ args := []string {"build" , "-B" , "--tags" , r .Tag }
191+ envs := []string {KoDockerRepoEnvVar + "=" + r .Repo }
192+ err := execCmdFn (envs , "ko" , args ... )
119193 if err != nil {
120194 return err
121195 }
122- if err := os .WriteFile (DockerfilePath , dockerfileContent , 0644 ); err != nil {
196+
197+ if r .Repo == KoLocalRepo {
198+ color .Green ("Image built successfully. Now you can publish the image" )
199+ } else {
200+ color .Green ("Image built and pushed successfully" )
201+ }
202+ return nil
203+ }
204+
205+ func (r * KoBuilder ) Validate () error {
206+ if err := r .GuaranteeKoInstalled (); err != nil {
123207 return err
124208 }
125- color .Green ("created Dockerfile" )
209+ // Find KO_DOCKER_REPO value from multiple places for `ko build`.
210+ if r .Repo != "" {
211+ return nil
212+ }
213+ if repo , ok := os .LookupEnv (KoDockerRepoEnvVar ); ok {
214+ r .Repo = repo
215+ return nil
216+ }
217+ r .Repo = "ko.local"
126218 return nil
127219}
220+
221+ func execCmd (envs []string , name string , args ... string ) error {
222+ cmd := exec .Command (name , args ... )
223+ if len (envs ) != 0 {
224+ cmd .Env = os .Environ ()
225+ cmd .Env = append (cmd .Env , envs ... )
226+ }
227+ cmd .Stdout = os .Stdout
228+ cmd .Stderr = os .Stderr
229+ err := cmd .Run ()
230+ return err
231+ }
0 commit comments