diff --git a/assets/knative/service.yaml b/assets/knative/service.yaml deleted file mode 100644 index 8aeaea1..0000000 --- a/assets/knative/service.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: serving.knative.dev/v1 -kind: Service -metadata: - name: helloworld-go - namespace: default -spec: - template: - spec: - containers: - - image: gcr.io/knative-samples/helloworld-go - env: - - name: TARGET - value: "Go Sample v1" \ No newline at end of file diff --git a/assets/knative/service.yaml.tmpl b/assets/knative/service.yaml.tmpl new file mode 100644 index 0000000..a1702b7 --- /dev/null +++ b/assets/knative/service.yaml.tmpl @@ -0,0 +1,10 @@ +apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: {{ .Name }} + namespace: default +spec: + template: + spec: + containers: + - image: {{ .RemoteTag }} diff --git a/go.mod b/go.mod index 46ded4d..06c81fa 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/a8m/envsubst v1.4.2 // indirect github.com/blendle/zapdriver v1.3.1 // indirect github.com/containerd/containerd v1.7.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect diff --git a/go.sum b/go.sum index d3513e6..5f7ebfd 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2y github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/hcsshim v0.10.0-rc.7 h1:HBytQPxcv8Oy4244zbQbe6hnOnx544eL5QPUqhJldz8= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg= +github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= diff --git a/src/cli/build.go b/src/cli/build.go index bc4ec45..d12f361 100644 --- a/src/cli/build.go +++ b/src/cli/build.go @@ -1,25 +1,16 @@ package cli import ( - "fmt" "path" - "github.com/nearform/k8s-kurated-addons-cli/src/services/docker" "github.com/nearform/k8s-kurated-addons-cli/src/utils/logger" "github.com/urfave/cli/v2" ) -func (c CLI) Build(cCtx *cli.Context) error { - repoName := cCtx.String("repo-name") - dockerFileName := cCtx.String("dockerfile-name") - project := c.newProject(cCtx) - docker, err := docker.New(project, dockerFileName, repoName) - if err != nil { - return fmt.Errorf("Creating docker service: %v", err) - } - - logger.PrintInfo("Dockerfile Location: " + path.Join(project.Directory, docker.DockerFileName)) - return docker.Build() +func (c *CLI) Build(cCtx *cli.Context) error { + project := c.getProject(cCtx) + logger.PrintInfo("Dockerfile Location: " + path.Join(project.Directory, c.DockerService.DockerFileName)) + return c.DockerService.Build() } func (c CLI) BuildCMD() *cli.Command { diff --git a/src/cli/cli.go b/src/cli/cli.go index 9bdfc4c..63ee77a 100644 --- a/src/cli/cli.go +++ b/src/cli/cli.go @@ -8,24 +8,59 @@ import ( "github.com/nearform/k8s-kurated-addons-cli/src/services/project" + "github.com/nearform/k8s-kurated-addons-cli/src/services/docker" "github.com/nearform/k8s-kurated-addons-cli/src/utils/defaults" + "github.com/nearform/k8s-kurated-addons-cli/src/utils/logger" "github.com/urfave/cli/v2" ) type CLI struct { - Resources embed.FS - CWD string + Resources embed.FS + CWD string + DockerService docker.DockerService + project project.Project + dockerImage docker.DockerImage } -func (c CLI) newProject(cCtx *cli.Context) project.Project { - return project.New( - cCtx.String("app-name"), - cCtx.String("project-directory"), +func (c *CLI) init(cCtx *cli.Context) { + + repoName := cCtx.String("repo-name") + dockerFileName := cCtx.String("dockerfile-name") + appName := cCtx.String("app-name") + version := cCtx.String("app-version") + projectDirectory := cCtx.String("project-directory") + + project := project.New( + appName, + projectDirectory, cCtx.String("runtime-version"), - cCtx.String("app-version"), + version, c.Resources, ) + + dockerImage := docker.DockerImage{ + Registry: repoName, + Name: appName, + Directory: projectDirectory, + Tag: version, + } + + dockerService, err := docker.New(project, dockerImage, dockerFileName) + if err != nil { + logger.PrintError("Error creating docker service", err) + } + + c.DockerService = dockerService + c.dockerImage = dockerImage + c.project = project +} + +func (c *CLI) getProject(cCtx *cli.Context) *project.Project { + if (c.project == project.Project{}) { + c.init(cCtx) + } + return &c.project } func (c CLI) Run() { diff --git a/src/cli/delete.go b/src/cli/delete.go index fac140a..85e5810 100644 --- a/src/cli/delete.go +++ b/src/cli/delete.go @@ -5,7 +5,7 @@ import ( "github.com/urfave/cli/v2" ) -func (c CLI) Delete(cCtx *cli.Context) error { +func (c *CLI) Delete(cCtx *cli.Context) error { config, err := knative.Config( cCtx.String("endpoint"), cCtx.String("token"), @@ -14,11 +14,11 @@ func (c CLI) Delete(cCtx *cli.Context) error { if err != nil { return err } - project := c.newProject(cCtx) + project := c.getProject(cCtx) return knative.Clean(config, project) } -func (c CLI) DeleteCMD() *cli.Command { +func (c *CLI) DeleteCMD() *cli.Command { return &cli.Command{ Name: "delete", Usage: "delete the knative service", diff --git a/src/cli/deploy.go b/src/cli/deploy.go index dad2b0f..8096dce 100644 --- a/src/cli/deploy.go +++ b/src/cli/deploy.go @@ -5,7 +5,7 @@ import ( "github.com/urfave/cli/v2" ) -func (c CLI) Deploy(cCtx *cli.Context) error { +func (c *CLI) Deploy(cCtx *cli.Context) error { config, err := knative.Config( cCtx.String("endpoint"), cCtx.String("token"), @@ -15,9 +15,9 @@ func (c CLI) Deploy(cCtx *cli.Context) error { if err != nil { return err } - project := c.newProject(cCtx) + project := c.getProject(cCtx) - return knative.Apply(config, project) + return knative.Apply(config, project, c.dockerImage) } func (c CLI) DeployCMD() *cli.Command { diff --git a/src/cli/onmain.go b/src/cli/onmain.go index 23cb7cf..42f4332 100644 --- a/src/cli/onmain.go +++ b/src/cli/onmain.go @@ -6,7 +6,7 @@ import ( "github.com/urfave/cli/v2" ) -func (c CLI) OnMainCMD() *cli.Command { +func (c *CLI) OnMainCMD() *cli.Command { flags := []cli.Flag{} flags = append(flags, Flags(Kubernetes)...) flags = append(flags, Flags(Build)...) diff --git a/src/cli/push.go b/src/cli/push.go index 4bf1127..ec03ede 100644 --- a/src/cli/push.go +++ b/src/cli/push.go @@ -1,29 +1,20 @@ package cli import ( - "fmt" - "github.com/docker/docker/api/types" - "github.com/nearform/k8s-kurated-addons-cli/src/services/docker" "github.com/urfave/cli/v2" ) -func (c CLI) Push(cCtx *cli.Context) error { - repoName := cCtx.String("repo-name") - dockerFileName := cCtx.String("dockerfile-name") - project := c.newProject(cCtx) - docker, err := docker.New(project, dockerFileName, repoName) - if err != nil { - return fmt.Errorf("Creating docker service: %v", err) - } - docker.AuthConfig = types.AuthConfig{ +func (c *CLI) Push(cCtx *cli.Context) error { + c.init(cCtx) + c.DockerService.AuthConfig = types.AuthConfig{ Username: cCtx.String("registry-user"), Password: cCtx.String("registry-password"), } - return docker.Push() + return c.DockerService.Push() } -func (c CLI) PushCMD() *cli.Command { +func (c *CLI) PushCMD() *cli.Command { return &cli.Command{ Name: "push", Usage: "push the container image to a registry", diff --git a/src/cli/template.go b/src/cli/template.go index ff6e785..73fb0b4 100644 --- a/src/cli/template.go +++ b/src/cli/template.go @@ -6,8 +6,8 @@ import ( "github.com/urfave/cli/v2" ) -func (c CLI) template(cCtx *cli.Context) error { - project := c.newProject(cCtx) +func (c *CLI) template(cCtx *cli.Context) error { + project := c.getProject(cCtx) content, err := project.Dockerfile() if err != nil { return fmt.Errorf("Getting docker file %v", err) @@ -16,7 +16,7 @@ func (c CLI) template(cCtx *cli.Context) error { return nil } -func (c CLI) TemplateCMD() *cli.Command { +func (c *CLI) TemplateCMD() *cli.Command { return &cli.Command{ Name: "template", Usage: "output the docker file used for this project", diff --git a/src/services/docker/docker-image.go b/src/services/docker/docker-image.go new file mode 100644 index 0000000..96a002c --- /dev/null +++ b/src/services/docker/docker-image.go @@ -0,0 +1,27 @@ +package docker + +import ( + "fmt" + "github.com/nearform/k8s-kurated-addons-cli/src/utils/defaults" +) + +type DockerImage struct { + Registry string + Name string + Directory string + Tag string +} + +func (dockerImage DockerImage) RemoteTag() string { + if dockerImage.Directory != defaults.ProjectDirectory { + return fmt.Sprintf("%s/%s/%s:%s", dockerImage.Registry, dockerImage.Name, dockerImage.Directory, dockerImage.Tag) + } + return fmt.Sprintf("%s/%s:%s", dockerImage.Registry, dockerImage.Name, dockerImage.Tag) +} + +func (dockerImage DockerImage) LocalTag() string { + if dockerImage.Directory != defaults.ProjectDirectory { + return fmt.Sprintf("%s/%s:%s", dockerImage.Name, dockerImage.Directory, dockerImage.Tag) + } + return fmt.Sprintf("%s:%s", dockerImage.Name, dockerImage.Tag) +} diff --git a/src/services/docker/docker.go b/src/services/docker/docker.go index 8431d09..38fa591 100644 --- a/src/services/docker/docker.go +++ b/src/services/docker/docker.go @@ -13,21 +13,22 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/docker/docker/pkg/archive" + "github.com/nearform/k8s-kurated-addons-cli/src/services/project" - "github.com/nearform/k8s-kurated-addons-cli/src/utils/defaults" + "github.com/nearform/k8s-kurated-addons-cli/src/utils/logger" ) type DockerService struct { project project.Project DockerFileName string - ContainerRepo string Client client.Client AuthConfig types.AuthConfig + dockerImage DockerImage } // Create a new instance of the DockerService -func New(project project.Project, dockerFileName string, containerRepo string) (DockerService, error) { +func New(project project.Project, dockerImage DockerImage, dockerFileName string) (DockerService, error) { client, err := getClient() if err != nil { return DockerService{}, err @@ -36,8 +37,8 @@ func New(project project.Project, dockerFileName string, containerRepo string) ( return DockerService{ project: project, DockerFileName: dockerFileName, - ContainerRepo: containerRepo, Client: *client, + dockerImage: dockerImage, }, nil } @@ -51,27 +52,9 @@ func getClient() (*client.Client, error) { return cli, nil } -func (ds DockerService) RemoteTag() string { - tag := ds.project.Version - directory := ds.project.Directory - if directory != defaults.ProjectDirectory { - return fmt.Sprintf("%s/%s/%s:%s", ds.ContainerRepo, ds.project.Name, directory, tag) - } - return fmt.Sprintf("%s/%s:%s", ds.ContainerRepo, ds.project.Name, tag) -} - -func (ds DockerService) LocalTag() string { - tag := ds.project.Version - directory := ds.project.Directory - if directory != defaults.ProjectDirectory { - return fmt.Sprintf("%s/%s:%s", ds.project.Name, directory, tag) - } - return fmt.Sprintf("%s:%s", ds.project.Name, tag) -} - func (ds DockerService) buildContext() (*bytes.Reader, error) { // Get the context for the docker build - existingBuildContext, err := archive.TarWithOptions(ds.project.Directory, &archive.TarOptions{}) + existingBuildContext, err := archive.TarWithOptions(ds.dockerImage.Directory, &archive.TarOptions{}) if err != nil { return nil, fmt.Errorf("Failed to create build context %v", err) } @@ -128,7 +111,7 @@ func (ds DockerService) buildContext() (*bytes.Reader, error) { // Build Docker image func (ds DockerService) Build() error { - logger.PrintInfo("Building " + ds.LocalTag()) + logger.PrintInfo("Building " + ds.dockerImage.LocalTag()) ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) defer cancel() @@ -142,7 +125,7 @@ func (ds DockerService) Build() error { buildOptions := types.ImageBuildOptions{ Context: combinedBuildContextReader, Dockerfile: ds.DockerFileName, - Tags: []string{ds.LocalTag()}, + Tags: []string{ds.dockerImage.LocalTag()}, Remove: true, } @@ -155,12 +138,13 @@ func (ds DockerService) Build() error { defer buildResponse.Body.Close() logger.PrintStream(buildResponse.Body) + return nil } // Push Docker image func (ds DockerService) Push() error { - logger.PrintInfo("Pushing to " + ds.RemoteTag()) + logger.PrintInfo("Pushing to " + ds.dockerImage.RemoteTag()) logger.PrintInfo("User: " + ds.AuthConfig.Username) ctx, cancel := context.WithTimeout(context.Background(), time.Second*120) defer cancel() @@ -173,12 +157,12 @@ func (ds DockerService) Push() error { RegistryAuth: base64.URLEncoding.EncodeToString(encodedJSON), } - err = ds.Client.ImageTag(ctx, ds.LocalTag(), ds.RemoteTag()) + err = ds.Client.ImageTag(ctx, ds.dockerImage.LocalTag(), ds.dockerImage.RemoteTag()) if err != nil { return fmt.Errorf("Tagging local image for remote %v", err) } - pushResponse, err := ds.Client.ImagePush(ctx, ds.RemoteTag(), ipo) + pushResponse, err := ds.Client.ImagePush(ctx, ds.dockerImage.RemoteTag(), ipo) defer pushResponse.Close() if err != nil { return fmt.Errorf("Failed to push docker image %v", err) diff --git a/src/services/docker/docker_test.go b/src/services/docker/docker_test.go index e72e182..736ae6b 100644 --- a/src/services/docker/docker_test.go +++ b/src/services/docker/docker_test.go @@ -8,22 +8,29 @@ import ( ) func TestLocalTag(t *testing.T) { + + dockerImage := DockerImage{ + Directory: defaults.ProjectDirectory, + Name: "test", + Tag: "v1.1.0", + } ds := DockerService{ project: project.Project{ Directory: defaults.ProjectDirectory, Name: "test", Version: "v1.1.0", }, + dockerImage: dockerImage, } - localTag := ds.LocalTag() + localTag := ds.dockerImage.LocalTag() expected := "test:v1.1.0" if localTag != expected { t.Fatalf("Expected '%s' got %s", expected, localTag) } - ds.project.Directory = "Subproject" + ds.dockerImage.Directory = "Subproject" - localTag = ds.LocalTag() + localTag = ds.dockerImage.LocalTag() expected = "test/Subproject:v1.1.0" if localTag != expected { t.Fatalf("Expected '%s' got %s", expected, localTag) @@ -31,26 +38,33 @@ func TestLocalTag(t *testing.T) { } func TestRemoteTag(t *testing.T) { + dockerImage := DockerImage{ + Registry: "example.org", + Directory: defaults.ProjectDirectory, + Name: "test", + Tag: "v1.1.0", + } + ds := DockerService{ - ContainerRepo: "example.org", project: project.Project{ Directory: defaults.ProjectDirectory, Name: "test", Version: "v1.1.0", }, + dockerImage: dockerImage, } - localTag := ds.RemoteTag() + remoteTag := ds.dockerImage.RemoteTag() expected := "example.org/test:v1.1.0" - if localTag != expected { - t.Fatalf("Expected '%s' got %s", expected, localTag) + if remoteTag != expected { + t.Fatalf("Expected '%s' got %s", expected, remoteTag) } - ds.project.Directory = "Subproject" + ds.dockerImage.Directory = "Subproject" - localTag = ds.RemoteTag() + remoteTag = ds.dockerImage.RemoteTag() expected = "example.org/test/Subproject:v1.1.0" - if localTag != expected { - t.Fatalf("Expected '%s' got %s", expected, localTag) + if remoteTag != expected { + t.Fatalf("Expected '%s' got %s", expected, remoteTag) } } diff --git a/src/services/k8s/knative.go b/src/services/k8s/knative.go index ad3c43b..b6f4755 100644 --- a/src/services/k8s/knative.go +++ b/src/services/k8s/knative.go @@ -1,14 +1,18 @@ package k8s import ( + "bytes" "context" "fmt" - "io/fs" "path" + "text/template" "time" + "github.com/nearform/k8s-kurated-addons-cli/src/services/docker" "github.com/nearform/k8s-kurated-addons-cli/src/services/project" + "github.com/nearform/k8s-kurated-addons-cli/src/utils/logger" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -33,12 +37,21 @@ func Config(endpoint string, token string, caCrt []byte) (*rest.Config, error) { }, nil } -func loadManifest(project project.Project) (*servingv1.Service, error) { - data, err := fs.ReadFile(project.Resources, path.Join("assets", "knative", "service.yaml")) +func loadManifest(project *project.Project, dockerImage docker.DockerImage) (*servingv1.Service, error) { + knativeTemplate := path.Join("assets", "knative", "service.yaml.tmpl") + template, err := template.ParseFS(project.Resources, knativeTemplate) if err != nil { return nil, fmt.Errorf("error reading the knative service yaml: %v", err) } + output := &bytes.Buffer{} + // TODO replace map[string]string{} with proper values + if err = template.Execute(output, dockerImage); err != nil { + return nil, err + } + + data := output.Bytes() + err = servingv1.AddToScheme(scheme.Scheme) if err != nil { return nil, fmt.Errorf("error adding Knative Serving scheme: %v", err) @@ -58,19 +71,20 @@ func loadManifest(project project.Project) (*servingv1.Service, error) { return service, nil } -func Apply(config *rest.Config, project project.Project) error { +func Apply(config *rest.Config, project *project.Project, dockerImage docker.DockerImage) error { logger.PrintInfo("Deploying Knative service to " + config.Host) ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5) defer cancel() - // Create a new Knative Serving client - servingClient, err := servingv1client.NewForConfig(config) + + service, err := loadManifest(project, dockerImage) if err != nil { - return fmt.Errorf("Error creating the knative client %v", err) + return err } - service, err := loadManifest(project) + // Create a new Knative Serving client + servingClient, err := servingv1client.NewForConfig(config) if err != nil { - return err + return fmt.Errorf("Error creating the knative client %v", err) } service.ObjectMeta.Namespace = "default" @@ -106,7 +120,7 @@ func Apply(config *rest.Config, project project.Project) error { return nil } -func Clean(config *rest.Config, project project.Project) error { +func Clean(config *rest.Config, project *project.Project) error { logger.PrintInfo("Deleting Knative service from " + config.Host) ctx := context.Background() diff --git a/src/services/k8s/knative_test.go b/src/services/k8s/knative_test.go index 5761fb9..67483cf 100644 --- a/src/services/k8s/knative_test.go +++ b/src/services/k8s/knative_test.go @@ -1,15 +1,14 @@ package k8s import ( + "encoding/base64" "fmt" - "testing" "os" - "encoding/base64" "path" + "testing" - + "github.com/nearform/k8s-kurated-addons-cli/src/services/docker" "github.com/nearform/k8s-kurated-addons-cli/src/services/project" - ) var root = "../../../" @@ -33,7 +32,7 @@ func TestConfig(t *testing.T) { if err != nil { t.Fatalf("Not possible to decode base64 token into a string: %v", err) } - + _, err = Config(endpoint, fmt.Sprintf("%x", decodedToken), []byte(decodedCert)) if err != nil { @@ -41,9 +40,9 @@ func TestConfig(t *testing.T) { } mockDummyValues := map[string]string{ - "caCrt": "certificatestring", + "caCrt": "certificatestring", "endpoint": "endpoint", - "token": "tokenstring", + "token": "tokenstring", } _, err = Config(mockDummyValues["endpoint"], mockDummyValues["token"], []byte(mockDummyValues["caCrt"])) @@ -54,18 +53,23 @@ func TestConfig(t *testing.T) { } - -func TestLoadManifest(t *testing.T){ - proj_knative := project.Project{Name: "knative_test", +func TestLoadManifest(t *testing.T) { + proj_knative := &project.Project{Name: "knative_test", Directory: path.Join(root, "example"), Resources: os.DirFS(root), } - _, err := loadManifest(proj_knative) + docker_image := docker.DockerImage{ + Registry: "example.com", + Directory: ".", + Name: "test", + Tag: "v1.1.0", + } + + _, err := loadManifest(proj_knative, docker_image) if err != nil { t.Fatalf(fmt.Sprintf("Error: %v", err)) } - -} \ No newline at end of file +} diff --git a/src/services/project/project_test.go b/src/services/project/project_test.go index 943e2c6..c987e70 100644 --- a/src/services/project/project_test.go +++ b/src/services/project/project_test.go @@ -60,7 +60,8 @@ func TestLoadDockerfile(t *testing.T) { func TestCorrectRuntime(t *testing.T) { - proj_runtime := Project{Name: "test", + proj_runtime := Project{ + Name: "test", Directory: path.Join(root, projects["node"]["directory"]), Resources: os.DirFS(root), RuntimeVersion: "30",