@@ -30,80 +30,121 @@ import (
3030
3131//go:embed embed/Dockerfile
3232var f embed.FS
33+ var execCmdFn = execCmd
34+ var execLookPathFn = exec .LookPath
3335
3436const (
35- ImageTag = "function:latest"
37+ // Builder Type
38+ Ko = "ko"
39+ Docker = "docker"
40+
41+ // Docker constant variables
42+ Image = "function:latest"
3643 DockerfilePath = "Dockerfile"
37- builtinDockerfilePath = "embed/Dockerfile"
44+ BuiltinDockerfilePath = "embed/Dockerfile"
45+
46+ // Ko constant variables
47+ KoDockerRepoEnvVar = "KO_DOCKER_REPO"
48+ KoLocalRepo = "ko.local"
3849)
3950
40- func NewBuildCmd (ctx context.Context ) * cobra. Command {
51+ func NewBuildRunner (ctx context.Context ) * BuildRunner {
4152 r := & BuildRunner {
4253 ctx : ctx ,
54+ Ko : & KoBuilder {},
55+ Docker : & DockerBuilder {},
4356 }
4457 r .Command = & cobra.Command {
4558 Use : "build" ,
46- Short : "build the KRM function as a docker image" ,
47- PreRunE : r .PreRunE ,
59+ Short : "build your KRM function to a container image" ,
4860 RunE : r .RunE ,
4961 }
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
62+ r .Command .Flags ().StringVarP (& r .BuilderType , "builder" , "b" , Ko ,
63+ "the image builder. `ko` is the default builder, which requires `go build`; `docker` is accepted, and " +
64+ " requires you to have docker installed and running" )
65+ r .Command .Flags ().StringVarP (& r .Docker .Image , "image" , "i" , Image ,
66+ fmt .Sprintf ("the image (with tag), default to %v" , Image ))
67+ r .Command .Flags ().StringVarP (& r .Docker .DockerfilePath , "dockerfile" , "f" , "" ,
68+ "path to the Dockerfile. If not given, using a default builtin Dockerfile" )
69+ r .Command .Flags ().StringVarP (& r .Ko .Repo , "repo" , "r" , "" ,
70+ "the image repo. default to ko.local" )
71+ r .Command .Flags ().StringVarP (& r .Ko .Tag , "tag" , "t" , "latest" ,
72+ "the ko image tag" )
73+ // TODO: Docker CLI uses `--tag` flag to refer to "image:tag", which could be confusing but broadly accepted.
74+ // We should better guide users on how to use "tag" and "image" flags for kfn.
75+ // Here we use "tag" for ko <tag> (same as `ko build --tag`) and "image" for docker <image:tag> (same as `docker build --tag`)
76+ return r
5577}
5678
5779type BuildRunner struct {
5880 ctx context.Context
5981 Command * cobra.Command
6082
61- Tag string
83+ BuilderType string
84+ Tag string
85+ Ko * KoBuilder
86+ Docker * DockerBuilder
87+ }
88+
89+ type Builder interface {
90+ Build () error
91+ Validate () error
92+ }
93+
94+ type DockerBuilder struct {
95+ Image string
6296 DockerfilePath string
6397}
6498
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
99+ type KoBuilder struct {
100+ Repo string
101+ Tag string
76102}
77103
78104func (r * BuildRunner ) RunE (cmd * cobra.Command , args []string ) error {
79- return r .runDockerBuild ()
105+ var builder Builder
106+ switch r .BuilderType {
107+ case Docker :
108+ builder = r .Docker
109+ case Ko :
110+ builder = r .Ko
111+ }
112+ if err := builder .Validate (); err != nil {
113+ return err
114+ }
115+ return builder .Build ()
80116}
81117
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 ()
118+ func (r * DockerBuilder ) Build () error {
119+ args := []string {"build" , "." , "-f" , r .DockerfilePath , "--tag" , r .Image }
120+ out , err := execCmdFn (nil , "docker" , args ... )
89121 if err != nil {
90- color .Red (strings .TrimSpace (errout .String ()))
91122 return err
92123 }
93- color . Green (out . String () )
94- color .Green ("Image %v builds successfully. Now you can publish the image" , r .Tag )
124+ fmt . Println (out )
125+ color .Green ("Image %v builds successfully. Now you can publish the image" , r .Image )
95126 return nil
96127}
97128
98- func (r * BuildRunner ) requireDocker () error {
99- _ , err := exec .LookPath ("docker" )
129+ func (r * DockerBuilder ) Validate () error {
130+ if err := r .validateDockerInstalled (); err != nil {
131+ return err
132+ }
133+ if r .fileExist () {
134+ return nil
135+ }
136+ return r .createDockerfile ()
137+ }
138+
139+ func (r * DockerBuilder ) validateDockerInstalled () error {
140+ _ , err := execLookPathFn ("docker" )
100141 if err != nil {
101142 return fmt .Errorf ("kfn requires that `docker` is installed and on the PATH" )
102143 }
103144 return nil
104145}
105146
106- func (r * BuildRunner ) dockerfileExist () bool {
147+ func (r * DockerBuilder ) fileExist () bool {
107148 if r .DockerfilePath == "" {
108149 r .DockerfilePath = DockerfilePath
109150 }
@@ -114,14 +155,74 @@ func (r *BuildRunner) dockerfileExist() bool {
114155 return true
115156}
116157
117- func (r * BuildRunner ) createDockerfile () error {
118- dockerfileContent , err := f .ReadFile (builtinDockerfilePath )
158+ func (r * DockerBuilder ) createDockerfile () error {
159+ dockerfileContent , err := f .ReadFile (BuiltinDockerfilePath )
160+ if err != nil {
161+ return err
162+ }
163+ if err = os .WriteFile (DockerfilePath , dockerfileContent , 0644 ); err != nil {
164+ return err
165+ }
166+ fmt .Println ("created Dockerfile" )
167+ return nil
168+ }
169+
170+ func (r * KoBuilder ) GuaranteeKoInstalled () error {
171+ _ , err := execLookPathFn ("ko" )
172+ if err == nil {
173+ return nil
174+ }
175+ if _ , err = execCmdFn (nil , "go" , "install" , "github.com/google/ko@latest" ); err != nil {
176+ return err
177+ }
178+ fmt .Println ("successfully installed ko" )
179+ return nil
180+ }
181+ func (r * KoBuilder ) Build () error {
182+ args := []string {"build" , "-B" , "--tags" , r .Tag }
183+ envs := []string {KoDockerRepoEnvVar + "=" + r .Repo }
184+ out , err := execCmdFn (envs , "ko" , args ... )
119185 if err != nil {
120186 return err
121187 }
122- if err := os .WriteFile (DockerfilePath , dockerfileContent , 0644 ); err != nil {
188+
189+ if r .Repo == KoLocalRepo {
190+ color .Green ("Image builds successfully. Now you can publish the image %v" , out )
191+ } else {
192+ color .Green ( "Image builds and pushed successfully: %v" , out )
193+ }
194+ return nil
195+ }
196+
197+ func (r * KoBuilder ) Validate () error {
198+ if err := r .GuaranteeKoInstalled (); err != nil {
123199 return err
124200 }
125- color .Green ("created Dockerfile" )
201+ // Find KO_DOCKER_REPO value from multiple places for `ko build`.
202+ if r .Repo != "" {
203+ return nil
204+ }
205+ if repo , ok := os .LookupEnv (KoDockerRepoEnvVar ); ok {
206+ r .Repo = repo
207+ return nil
208+ }
209+ r .Repo = "ko.local"
126210 return nil
127211}
212+
213+ func execCmd (envs []string , name string , args ... string ) (string , error ) {
214+ cmd := exec .Command (name , args ... )
215+ if len (envs ) != 0 {
216+ cmd .Env = os .Environ ()
217+ cmd .Env = append (cmd .Env , envs ... )
218+ }
219+ var out , errout bytes.Buffer
220+ cmd .Stdout = & out
221+ cmd .Stderr = & errout
222+ err := cmd .Run ()
223+ if err != nil {
224+ color .Red (strings .TrimSpace (errout .String ()))
225+ return "" , err
226+ }
227+ return strings .TrimSpace (out .String ()), nil
228+ }
0 commit comments