Add tar.gz support to the go Proxy (#191)

* Add tar.gz support to the go Proxy

This new feature will allow for more versatile deployment packages and greater flexibility in handling compressed files.
Giving an alternative to zip files.

this feature does not impact the current functionality of the go Proxy

* Add license header and fix eol

* Implement suggestions

* Remove debugging statement

* Remove unneeded fmt import
diff --git a/openwhisk/extractor.go b/openwhisk/extractor.go
index 097de0a..adf4cc7 100644
--- a/openwhisk/extractor.go
+++ b/openwhisk/extractor.go
@@ -64,6 +64,10 @@
 		}
 		Debug("Extract Action, assuming a zip")
 		return file, Unzip(*buf, newDir)
+
+	} else if IsGz(*buf) {
+		Debug("Extract Action, assuming a tar.gz")
+		return file, UnTar(*buf, newDir)
 	}
 	return file, ioutil.WriteFile(file, *buf, 0755)
 }
diff --git a/openwhisk/filetype.go b/openwhisk/filetype.go
index 20fe388..ed255d0 100644
--- a/openwhisk/filetype.go
+++ b/openwhisk/filetype.go
@@ -65,3 +65,13 @@
 		(buf[2] == 0x3 || buf[2] == 0x5 || buf[2] == 0x7) &&
 		(buf[3] == 0x4 || buf[3] == 0x6 || buf[3] == 0x8)
 }
+
+// IsGz checks if the given file is a valid tar.gz file
+func IsGz(buf []byte) bool {
+	// Magic number: The first two bytes are fixed (0x1f and 0x8b), which represent the magic number of a gzip file.
+	// Compression method: The third byte indicates the compression method used. For gzip files, this is always  (deflate).
+	return len(buf) > 3 &&
+		buf[0] == 0x1f &&
+		buf[1] == 0x8b &&
+		buf[2] == 0x08
+}
diff --git a/openwhisk/tar.go b/openwhisk/tar.go
new file mode 100644
index 0000000..cc683f7
--- /dev/null
+++ b/openwhisk/tar.go
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 openwhisk
+
+import (
+	"archive/tar"
+	"bytes"
+	"compress/gzip"
+	"io"
+	"os"
+	"path/filepath"
+)
+
+func openTar(src []byte) (*tar.Reader, error) {
+	// Create a new bytes.Reader from the input byte slice
+	reader := bytes.NewReader(src)
+
+	// Create a new gzip.Reader from the bytes.Reader
+	gzipReader, err := gzip.NewReader(reader)
+	if err != nil {
+		return nil, err
+	}
+	defer gzipReader.Close()
+
+	// Create a new tar.Reader from the gzip.Reader
+	tarReader := tar.NewReader(gzipReader)
+
+	return tarReader, nil
+}
+
+func UnTar(src []byte, dest string) error {
+	r, err := openTar(src)
+	if err != nil {
+		return err
+	}
+	Debug("open Tar")
+	os.MkdirAll(dest, 0755)
+	for {
+		header, err := r.Next()
+		switch {
+
+		// if no more files are found return
+		case err == io.EOF:
+			return nil
+
+		// return any other error
+		case err != nil:
+			return err
+
+		// if the header is nil, just skip it (not sure how this happens)
+		case header == nil:
+			continue
+		}
+
+		// the target location where the dir/file should be created
+		target := filepath.Join(dest, header.Name)
+		// isLink := header.FileInfo().Mode()&os.ModeSymlink == os.ModeSymlink
+
+		// the following switch could also be done using fi.Mode(), not sure if there
+		// a benefit of using one vs. the other.
+		// fi := header.FileInfo()
+
+		// check the file type
+		switch header.Typeflag {
+
+		// if its a dir and it doesn't exist create it
+		case tar.TypeDir:
+			if _, err := os.Stat(target); err != nil {
+				if err := os.MkdirAll(target, 0755); err != nil {
+					return err
+				}
+			}
+
+		// if it's a file create it
+		case tar.TypeReg:
+			f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
+			if err != nil {
+				return err
+			}
+
+			// copy over contents
+			if _, err := io.Copy(f, r); err != nil {
+				return err
+			}
+
+			// manually close here after each file operation; defering would cause each file close
+			// to wait until all operations have completed.
+			f.Close()
+		}
+	}
+}