Skip to content

Commit

Permalink
Adding support for cross compatability darwin / linux
Browse files Browse the repository at this point in the history
  • Loading branch information
simplyzee committed Sep 14, 2019
1 parent 56308dc commit e00f78a
Show file tree
Hide file tree
Showing 15 changed files with 699 additions and 83 deletions.
111 changes: 28 additions & 83 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,95 +1,40 @@
-include .env
# ########################################################## #
# Makefile for Golang Project
# Includes cross-compiling, installation, cleanup
# ########################################################## #

VERSION := $(shell git describe --tags)
BUILD := $(shell git rev-parse --short HEAD)
PROJECTNAME := $(shell basename "$(PWD)")
# Check for required command tools to build or stop immediately
EXECUTABLES = git go find pwd
K := $(foreach exec,$(EXECUTABLES),\
$(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH)))

# Go related variables.
GOBASE := $(shell pwd)
GOPATH := $(GOBASE)/vendor:$(GOBASE)
GOBIN := $(GOBASE)/bin
GOFILES := $(wildcard *.go)
ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))

# Use linker flags to provide version/build settings
LDFLAGS=-ldflags "-X=main.Version=$(VERSION) -X=main.Build=$(BUILD)"
BINARY=kubemngr
VERSION=0.0.1
BUILD=`git rev-parse HEAD`
PLATFORMS=darwin linux #windows
ARCHITECTURES=386 amd64

# Redirect error output to a file, so we can show it in development mode.
STDERR := /tmp/.$(PROJECTNAME)-stderr.txt
# Setup linker flags option for build that interoperate with variable names in src code
LDFLAGS=-ldflags "-X main.Version=${VERSION} -X main.Build=${BUILD}"

# PID file will keep the process id of the server
PID := /tmp/.$(PROJECTNAME).pid
default: build

# Make is verbose in Linux. Make it silent.
MAKEFLAGS += --silent
all: clean build_all install

## install: Install missing dependencies. Runs `go get` internally. e.g; make install get=github.com/foo/bar
install: go-get
build:
go build ${LDFLAGS} -o ${BINARY}

## start: Start in development mode. Auto-starts when code changes.
start:
@bash -c "trap 'make stop' EXIT; $(MAKE) clean compile start-server watch run='make clean compile start-server'"
build_all:
$(foreach GOOS, $(PLATFORMS),\
$(foreach GOARCH, $(ARCHITECTURES), $(shell export GOOS=$(GOOS); export GOARCH=$(GOARCH); go build -v -o $(BINARY)-$(GOOS)-$(GOARCH))))

## stop: Stop development mode.
stop: stop-server
install:
go install ${LDFLAGS}

start-server: stop-server
@echo " > $(PROJECTNAME) is available at $(ADDR)"
@-$(GOBIN)/$(PROJECTNAME) 2>&1 & echo $$! > $(PID)
@cat $(PID) | sed "/^/s/^/ \> PID: /"

stop-server:
@-touch $(PID)
@-kill `cat $(PID)` 2> /dev/null || true
@-rm $(PID)

## watch: Run given command when code changes. e.g; make watch run="echo 'hey'"
watch:
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) yolo -i . -e vendor -e bin -c "$(run)"

restart-server: stop-server start-server

## compile: Compile the binary.
compile:
@-touch $(STDERR)
@-rm $(STDERR)
@-$(MAKE) -s go-compile 2> $(STDERR)
@cat $(STDERR) | sed -e '1s/.*/\nError:\n/' | sed 's/make\[.*/ /' | sed "/^/s/^/ /" 1>&2

## exec: Run given command, wrapped with custom GOPATH. e.g; make exec run="go test ./..."
exec:
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) $(run)

## clean: Clean build files. Runs `go clean` internally.
# Remove only what we've created
clean:
@-rm $(GOBIN)/$(PROJECTNAME) 2> /dev/null
@-$(MAKE) go-clean

go-compile: go-get go-build

go-build:
@echo " > Building binary..."
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go build $(LDFLAGS) -o $(GOBIN)/$(PROJECTNAME) $(GOFILES)

go-generate:
@echo " > Generating dependency files..."
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go generate $(generate)

go-get:
@echo " > Checking if there is any missing dependencies..."
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go get $(get)

go-install:
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go install $(GOFILES)

go-clean:
@echo " > Cleaning build cache"
@GOPATH=$(GOPATH) GOBIN=$(GOBIN) go clean
find ${ROOT_DIR} -name '${BINARY}[-?][a-zA-Z0-9]*[-?][a-zA-Z0-9]*' -delete

.PHONY: help
all: help
help: Makefile
@echo
@echo " Choose a command run in "$(PROJECTNAME)":"
@echo
@sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
@echo
.PHONY: check clean install build_all all
203 changes: 203 additions & 0 deletions cmd/install_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// +build darwin

/*
Copyright © 2019 NAME HERE <EMAIL ADDRESS>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd

import (
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"syscall"

"github.com/dustin/go-humanize"
_ "github.com/google/go-github/v27/github"
"github.com/spf13/cobra"
)

var sys, machine string

type WriteCounter struct {
Total uint64
}

// installCmd represents the install command
var installCmd = &cobra.Command{
Use: "install",
Short: "A tool manage different kubectl versions inside a workspace.",
//RunE: func(cmd *cobra.Command, args []string) error {
// return errors.New("provide a kubectl version")
//},
Run: func(cmd *cobra.Command, args []string) {
err := DownloadKubectl(args[0])

if err != nil {
log.Fatal(err)
}
},
}

func init() {
rootCmd.AddCommand(installCmd)

// Here you will define your flags and configuration settings.

// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// installCmd.PersistentFlags().String("foo", "", "A help for foo")

// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// installCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

//DownloadKubectl - download user specified version of kubectl
func DownloadKubectl(version string) error {

// TODO use tmp directory to download instead of kubemngr.
// This was failing originally with the error: invalid cross-link device
// filepath := "/tmp/"

// TODO better sanity check for checking arg is valid
if len(version) == 0 {
log.Fatal(0)
}

// Get user home directory path
homeDir, err := os.UserHomeDir()
if err != nil {
log.Fatal(err)
}

// Check if current version already exists
_, err = os.Stat(homeDir + "/.kubemngr/kubectl-" + version)
if err == nil {
log.Printf("kubectl version %s already exists", version)
return nil
}

// Create temp file of kubectl version in tmp directory
out, err := os.Create(homeDir + "/.kubemngr/kubectl-" + version + ".tmp")
if err != nil {
log.Fatal(err)
}

defer out.Close()

// Get OS information to filter download type i.e linux / darwin
uname := GetOSInfo()

// TODO refactor me
// doesn't work on OSX
if ArrayToString(uname.Sysname) == "Linux" {
sys = "linux"
} else if ArrayToString(uname.Sysname) == "Darwin" {
sys = "darwin"
} else {
sys = "UNKNOWN"
fmt.Println("Unknown system")
}

if ArrayToString(uname.Machine) == "arm" {
machine = "arm"
} else if ArrayToString(uname.Machine) == "arm64" {
machine = "arm64"
} else if ArrayToString(uname.Machine) == "x86_64" {
machine = "amd64"
} else {
machine = "UNKNOWN"
fmt.Println("Unknown machine")
}

url := "https://storage.googleapis.com/kubernetes-release/release/" + version + "/bin/" + sys + "/" + machine + "/kubectl"

resp, err := http.Get(url)
if err != nil {
log.Fatal(err)
}

defer resp.Body.Close()

// Initialise WriteCounter and copy the contents of the response body to the tmp file
counter := &WriteCounter{}
_, err = io.Copy(out, io.TeeReader(resp.Body, counter))
if err != nil {
log.Fatal(err)
}

// The progress use the same line so print a new line once it's finished downloading
fmt.Println()

// Set executable permissions on the kubectl binary
if err := os.Chmod(homeDir+"/.kubemngr/kubectl-"+version+".tmp", 0755); err != nil {
log.Fatal(err)
}

// Rename the tmp file back to the original file and store it in the kubemngr directory
currentFilePath := homeDir + "/.kubemngr/kubectl-" + version + ".tmp"
newFilePath := homeDir + "/.kubemngr/kubectl-" + version

err = os.Rename(currentFilePath, newFilePath)
if err != nil {
log.Fatal(err)
}

return nil
}

// GetOSInfo - Get operating system information of machine
func GetOSInfo() syscall.Utsname {
var uname syscall.Utsname

if err := syscall.Uname(&uname); err != nil {
fmt.Printf("Uname: %v", err)
}

return uname
}

func (wc *WriteCounter) Write(p []byte) (int, error) {
n := len(p)
wc.Total += uint64(n)
wc.PrintProgress()
return n, nil
}

// PrintProgress - Helper function to print progress of a download
func (wc WriteCounter) PrintProgress() {
// Clear the line by using a character return to go back to the start and remove
// the remaining characters by filling it with spaces
fmt.Printf("\r%s", strings.Repeat(" ", 50))

// Return again and print current status of download
// We use the humanize package to print the bytes in a meaningful way (e.g. 10 MB)
fmt.Printf("\rDownloading... %s complete", humanize.Bytes(wc.Total))
}

func ArrayToString(x [65]int8) string {
var buf [65]byte
for i, b := range x {
buf[i] = byte(b)
}
str := string(buf[:])
if i := strings.Index(str, "\x00"); i != -1 {
str = str[:i]
}
return str
}
2 changes: 2 additions & 0 deletions cmd/install.go → cmd/install_linux.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// +build linux

/*
Copyright © 2019 NAME HERE <EMAIL ADDRESS>
Expand Down
Loading

0 comments on commit e00f78a

Please sign in to comment.