Commit 2ae79dcb authored by Mickael Desfrenes's avatar Mickael Desfrenes
Browse files

first

parent a03e801d
package main
import (
"archive/tar"
"bytes"
"compress/gzip"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"time"
)
type Transformation struct {
Name string `json:"name"`
Options map[string]string `json:"options,omitempty"`
}
type Job struct {
Uuid string `json:"-"`
Files []string `json:"-"`
Transformations []Transformation `json:"transformations"`
NotifyHook string `json:"notify_hook,omitempty"`
ResultFilePath string `json:"-"`
}
func (job *Job) AddFile(filepath string) {
job.Files = append(job.Files, filepath)
}
func (job *Job) AddTransformation(transformation Transformation) {
job.Transformations = append(job.Transformations, transformation)
}
func addFileToTgz(tarWriter *tar.Writer, filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
if stat, err := file.Stat(); err == nil {
header := new(tar.Header)
header.Name = filepath.Base(filePath) // shorten file filePath here
header.Size = stat.Size()
header.Mode = int64(stat.Mode())
header.ModTime = stat.ModTime()
if err := tarWriter.WriteHeader(header); err != nil {
return err
}
if _, err := io.Copy(tarWriter, file); err != nil {
return err
}
}
return nil
}
func (job *Job) MakeArchive() *os.File {
jobJson, err := json.MarshalIndent(job, "", " ")
if err != nil {
log.Fatal(err)
}
tmpTgzFile, err := ioutil.TempFile(os.TempDir(), "*.tar.gz")
if err != nil {
log.Fatal("Cannot create temporary file", err)
}
gzipWriter := gzip.NewWriter(tmpTgzFile)
tarWriter := tar.NewWriter(gzipWriter)
jsonHeader := new(tar.Header)
jsonHeader.Name = "job.json"
jsonHeader.Size = int64(len(jobJson))
jsonHeader.Mode = 420 // blaze it
if err := tarWriter.WriteHeader(jsonHeader); err != nil {
log.Fatal(err)
}
if _, err := io.Copy(tarWriter, bytes.NewReader(jobJson)); err != nil {
log.Fatal(err)
}
for _, filePath := range job.Files {
if err := addFileToTgz(tarWriter, filePath); err != nil {
log.Fatal(err)
}
}
tarWriter.Close()
gzipWriter.Close()
tmpTgzFile.Seek(0, 0)
return tmpTgzFile
}
type CirceClient struct {
Endpoint string
SecretKey string
ApplicationUuid string
}
func NewCirceClient(Endpoint string, SecretKey string, ApplicationUuid string) *CirceClient {
client := new(CirceClient)
client.ApplicationUuid = ApplicationUuid
client.SecretKey = SecretKey
client.Endpoint = Endpoint
return client
}
func (client CirceClient) NewJob() Job {
return Job{
Uuid: "",
Files: []string{},
Transformations: []Transformation{},
NotifyHook: "",
ResultFilePath: "",
}
}
func FileHmacHexDigest(fp *os.File, secret string) string {
hmacFile := hmac.New(sha256.New, []byte(secret))
bytesBuffer := make([]byte, 128)
for {
bytesRead, err := fp.Read(bytesBuffer)
if err != nil {
if err != io.EOF {
log.Fatal(err)
}
if bytesRead > 0 { // handle files smaller than 128 bytes before the break
hmacFile.Write(bytesBuffer[:bytesRead])
}
break
}
hmacFile.Write(bytesBuffer[:bytesRead])
}
hmacDigest := hex.EncodeToString(hmacFile.Sum(nil))
fp.Seek(0, 0)
return hmacDigest
}
func checkerr(e error) {
if e != nil {
panic(e)
}
}
func ExtractTarGz(gzipStream io.Reader, destination string) {
uncompressedStream, err := gzip.NewReader(gzipStream)
if err != nil {
log.Fatal("ExtractTarGz: NewReader failed")
}
tarReader := tar.NewReader(uncompressedStream)
for true {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("ExtractTarGz: Next() failed: %s", err.Error())
}
if header.Name != "/" && header.Name != "job.json" && header.Name != "out.log" {
switch header.Typeflag {
case tar.TypeDir:
if err := os.Mkdir(header.Name, 0755); err != nil {
log.Fatalf("ExtractTarGz: Mkdir() failed: %s", err.Error())
}
case tar.TypeReg:
outFile, err := os.Create(destination)
if err != nil {
log.Fatalf("ExtractTarGz: Create() failed: %s", err.Error())
}
defer outFile.Close()
if _, err := io.Copy(outFile, tarReader); err != nil {
log.Fatalf("ExtractTarGz: Copy() failed: %s", err.Error())
}
default:
log.Fatalf(
"ExtractTarGz: uknown type: %s in %s",
header.Typeflag,
header.Name)
}
}
}
}
func StringHmacHexDigest(payload string, secret string) string {
hmacString := hmac.New(sha256.New, []byte(secret))
hmacString.Write([]byte(payload))
hmacDigest := hex.EncodeToString(hmacString.Sum(nil))
return hmacDigest
}
func (client CirceClient) SendJob(job *Job) error {
var archiveFilePointer = job.MakeArchive() //defer archiveFilePointer.Close()
hmacDigest := FileHmacHexDigest(archiveFilePointer, client.SecretKey)
httpClient := &http.Client{}
postRequest, err := http.NewRequest("POST", client.Endpoint+"job/", archiveFilePointer)
checkerr(err)
postRequest.Header.Add("Authorization", client.ApplicationUuid+" "+hmacDigest)
response, err := httpClient.Do(postRequest)
if err != nil {
return err
}
defer response.Body.Close()
jobUuid, _ := ioutil.ReadAll(response.Body)
job.Uuid = string(jobUuid)
defer os.Remove(archiveFilePointer.Name())
return nil
}
func (client CirceClient) Poll(job *Job, destinationFilePath string, timeout int) (success bool, err error) {
if job.Uuid == "" {
err := client.SendJob(job)
if err != nil {
return false, err
}
}
then := time.Now()
hmacDigest := StringHmacHexDigest(job.Uuid, client.SecretKey)
httpClient := &http.Client{}
for {
getRequest, err := http.NewRequest("GET", client.Endpoint+"job/"+job.Uuid, nil)
if err != nil {
log.Print(err)
os.Exit(1)
}
getRequest.Header.Add("Authorization", client.ApplicationUuid+" "+hmacDigest)
resp, err := httpClient.Do(getRequest)
if err != nil {
log.Print(err)
os.Exit(1)
}
if resp.StatusCode == 200 {
out, err := os.Create(destinationFilePath)
if err != nil {
return false, err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
return false, err
}
return true, nil
}
duration := time.Since(then)
if int(duration.Seconds()) > timeout {
return false, errors.New("poll timeout")
}
time.Sleep(time.Second)
}
}
func showHelpAndExit() {
message := `-= Command-line client to the Circe API =-
Usage:
%s [source file] [transformation] [destination file]
Example:
%s test1.html html2pdf test1.pdf
`
fmt.Printf(message, os.Args[0], os.Args[0])
os.Exit(0)
}
func getEnv(key, fallback string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return fallback
}
func main() {
circeEndpoint := getEnv("CIRCE_ENDPOINT", "https://circe.bontards.com/")
circeSecret := getEnv("CIRCE_SECRET", "@Jy_<b)Fy%/TkVlJ=T#kZIyFt3E_?B.T")
circeAppUuid := getEnv("CIRCE_APP_UUID", "7f5a3ce0-a729-4e41-80df-cad73aee96b2")
/*
if circeEndpoint == "" || circeAppUuid == "" || circeSecret == "" {
log.Fatalln("Please set the following environment variables: CIRCE_ENDPOINT, CIRCE_SECRET, CIRCE_APP_UUID")
}
*/
args := os.Args[1:]
if len(args) < 3 {
showHelpAndExit()
}
filesToTransform := strings.Split(args[0], ",")
transformations := strings.Split(args[1], ",")
destination := args[2]
circe := NewCirceClient(circeEndpoint, circeSecret, circeAppUuid)
job := circe.NewJob()
for _, file := range filesToTransform {
job.AddFile(file)
}
for _, transformation := range transformations {
job.AddTransformation(Transformation{
Name: transformation,
})
}
tmpTgzFile, err := ioutil.TempFile(os.TempDir(), "*.tar.gz")
checkerr(err)
defer os.Remove(tmpTgzFile.Name())
_, err = circe.Poll(&job, tmpTgzFile.Name(), 30)
checkerr(err)
ExtractTarGz(tmpTgzFile, destination)
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment