Skip to content

Commit

Permalink
Merge pull request #16 from banzaicloud/apply
Browse files Browse the repository at this point in the history
implement apply subcommand
  • Loading branch information
bonifaido authored Jun 19, 2020
2 parents 4a3222a + 6f27d8f commit d5d60c7
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 4 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ jobs:
runs-on: ubuntu-latest
steps:

- name: Set up Go 1.12
- name: Set up Go 1.14
uses: actions/setup-go@v1
with:
go-version: 1.12
go-version: 1.14
id: go

- name: Check out code into the Go module directory
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/banzaicloud/kurun
go 1.12

require (
github.com/ghodss/yaml v1.0.0
github.com/imdario/mergo v0.3.7 // indirect
github.com/spf13/cobra v0.0.5
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect
Expand Down
3 changes: 2 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415 h1:WSBJMqJbLxsn+bTCPyPYZfqHdJmc8MK4wrBjMft6BAM=
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -120,7 +122,6 @@ k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719 h1:uV4S5IB5g4Nvi+TBVNf3e9
k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA=
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab h1:E8Fecph0qbNsAbijJJQryKu4Oi9QTp5cVpjTE+nqg6g=
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab/go.mod h1:E95RaSlHr79aHaX0aGSwcPNfygDiPKOVXdmivCIZT0k=
k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68=
k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
Expand Down
220 changes: 219 additions & 1 deletion kurun.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package main

import (
"bytes"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"os/exec"
Expand All @@ -14,19 +17,26 @@ import (
"syscall"
"time"

"github.com/ghodss/yaml"
"github.com/spf13/cobra"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
k8sYaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/kubernetes"
clientscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)

const imageTag = "kurun"
const kurunSchemaPrefix = "kurun://"

var serviceAccount string
var servicePort int
Expand All @@ -35,6 +45,7 @@ var tlsSecret string
var podEnv []string
var namespace string
var labels []string
var files []string
var localPort int

func getKubeConfig() (*rest.Config, error) {
Expand Down Expand Up @@ -461,6 +472,211 @@ var portForwardCmd = &cobra.Command{
},
}

func buildImage(goFiles []string) (string, error) {
hash := sha256.New()
for _, goFile := range goFiles {
absGoFile, err := filepath.Abs(goFile)
if err != nil {
return "", err
}

_, err = hash.Write([]byte(absGoFile))
if err != nil {
return "", err
}
}

imageTag := fmt.Sprintf("kurun-%x", hash.Sum(nil))
directory := "/tmp/kurun/" + imageTag

os.MkdirAll(directory, os.ModePerm)

goBuildArgs := []string{"build", "-o", directory + "/main"}
for _, gofile := range goFiles {
goBuildArgs = append(goBuildArgs, gofile)
}
goBuildCommand := exec.Command("go", goBuildArgs...)
goBuildCommand.Stderr = os.Stderr
goBuildCommand.Stdout = os.Stdout
env := os.Environ()
env = append(env, "GOOS=linux", "CGO_ENABLED=0")
goBuildCommand.Env = env

println(goBuildCommand.String())

if err := goBuildCommand.Run(); err != nil {
return "", err
}

file, err := os.Create(directory + "/Dockerfile")
if err != nil {
return "", err
}
defer file.Close()

fmt.Fprintf(file, "FROM alpine\nADD main /\n")

dockerBuildCommand := exec.Command("docker", "build", "-t", imageTag, directory)
dockerBuildCommand.Stderr = os.Stderr
dockerBuildCommand.Stdout = os.Stdout

if err := dockerBuildCommand.Run(); err != nil {
return "", err
}

kindCluster, err := isKindCluster()
if err != nil {
return "", err
}

if kindCluster {
kindLoadCommand := exec.Command("kind", "load", "docker-image", imageTag)
kindLoadCommand.Stderr = os.Stderr
kindLoadCommand.Stdout = os.Stdout

if err := kindLoadCommand.Run(); err != nil {
return "", err
}
}

return imageTag, nil
}

var applyCmd = &cobra.Command{
Use: "apply [flags] -f pod.yaml",
Short: "Just like `kubectl apply -f pod.yaml` but images are built from local source code.",
RunE: func(cmd *cobra.Command, args []string) error {

var rawResources [][]byte

for _, manifest := range files {
manifestData, err := ioutil.ReadFile(manifest)
if err != nil {
return err
}

decoder := k8sYaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(manifestData), 4096)

var obj *unstructured.Unstructured

for {
err := decoder.Decode(&obj)
if err != nil && err != io.EOF {
return fmt.Errorf("failed to unmarshal manifest: %s", err)
}

if obj == nil {
break
}

var resource interface{}

switch obj.GetKind() {
case "Pod":
pod, err := unstructuredToPod(obj)
if err != nil {
return err
}

for i, c := range pod.Spec.Containers {
if strings.HasPrefix(c.Image, kurunSchemaPrefix) {
goFilesPath := strings.TrimPrefix(c.Image, kurunSchemaPrefix)
pod.Spec.Containers[i].Image, err = buildImage([]string{goFilesPath})
if err != nil {
return err
}

pod.Spec.Containers[i].ImagePullPolicy = corev1.PullNever
}
}

resource = pod

case "Deployment":
deployment, err := unstructuredToDeployment(obj)
if err != nil {
return err
}

for i, c := range deployment.Spec.Template.Spec.Containers {
if strings.HasPrefix(c.Image, kurunSchemaPrefix) {
goFilesPath := strings.TrimPrefix(c.Image, kurunSchemaPrefix)
deployment.Spec.Template.Spec.Containers[i].Image, err = buildImage([]string{goFilesPath})
if err != nil {
return err
}

deployment.Spec.Template.Spec.Containers[i].ImagePullPolicy = corev1.PullNever
}
}

resource = deployment
default:
resource = obj
}

rawResource, err := yaml.Marshal(resource)
if err != nil {
return err
}

rawResources = append(rawResources, rawResource)

obj = nil
}
}

resourceBuffer := bytes.NewBuffer(nil)

for _, rawResource := range rawResources {
_, err := resourceBuffer.Write(rawResource)
if err != nil {
return err
}

_, err = resourceBuffer.WriteString("\n---\n")
if err != nil {
return err
}
}

kubectlArgs := append([]string{"apply", "-f", "-"}, args...)

kubectlCommand := exec.Command("kubectl", kubectlArgs...)
kubectlCommand.Stdin = resourceBuffer
kubectlCommand.Stderr = os.Stderr
kubectlCommand.Stdout = os.Stdout

if err := kubectlCommand.Run(); err != nil {
cmd.SilenceUsage = true
cmd.SilenceErrors = true
return err
}

return nil
},
}

func unstructuredToPod(obj *unstructured.Unstructured) (*v1.Pod, error) {
json, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
}
pod := new(v1.Pod)
err = runtime.DecodeInto(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), json, pod)
return pod, err
}

func unstructuredToDeployment(obj *unstructured.Unstructured) (*appsv1.Deployment, error) {
json, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
}
deployment := new(appsv1.Deployment)
err = runtime.DecodeInto(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), json, deployment)
return deployment, err
}

func main() {
runCmd.PersistentFlags().StringVar(&serviceAccount, "serviceaccount", "", "Service account to set for the pod")
runCmd.PersistentFlags().StringArrayVarP(&podEnv, "env", "e", []string{}, "Environment variables to pass to the pod's containers")
Expand All @@ -471,9 +687,11 @@ func main() {
portForwardCmd.PersistentFlags().StringVar(&tlsSecret, "tlssecret", "", "Use the certs for ghostunnel")
portForwardCmd.PersistentFlags().IntVar(&localPort, "localport", 8444, "Local port to use for port-forwarding")

applyCmd.PersistentFlags().StringSliceVarP(&files, "filename", "f", []string{}, "Filename, directory, or URL to files to use to create the resource")

rootCmd := &cobra.Command{Use: "kurun"}
rootCmd.PersistentFlags().StringVar(&namespace, "namespace", "default", "Namespace to use for the Pod/Service")
rootCmd.AddCommand(runCmd, portForwardCmd)
rootCmd.AddCommand(runCmd, portForwardCmd, applyCmd)
if err := rootCmd.Execute(); err != nil {
os.Exit(2)
}
Expand Down

0 comments on commit d5d60c7

Please sign in to comment.