Skip to content

Commit

Permalink
Add commands for working with packages [BETA]
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Anagrius committed Aug 19, 2020
1 parent c74d7f9 commit c179e99
Show file tree
Hide file tree
Showing 10 changed files with 527 additions and 7 deletions.
15 changes: 10 additions & 5 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package api
import (
"bytes"
"context"
"io"
"net/http"

"github.com/shurcooL/graphql"
Expand Down Expand Up @@ -71,24 +72,28 @@ func (c *Client) Mutate(mutation interface{}, variables map[string]interface{})
return graphqlErr
}

func (c *Client) HTTPRequest(httpMethod string, path string, body *bytes.Buffer) (*http.Response, error) {
return c.HTTPRequestContext(context.Background(), httpMethod, path, body)
// JSONContentType is "application/json"
const JSONContentType string = "application/json"

func (c *Client) HTTPRequest(httpMethod string, path string, body io.Reader) (*http.Response, error) {
return c.HTTPRequestContext(context.Background(), httpMethod, path, body, JSONContentType)
}

func (c *Client) HTTPRequestContext(ctx context.Context, httpMethod string, path string, body *bytes.Buffer) (*http.Response, error) {
func (c *Client) HTTPRequestContext(ctx context.Context, httpMethod string, path string, body io.Reader, contentType string) (*http.Response, error) {
if body == nil {
body = bytes.NewBuffer([]byte(""))
body = bytes.NewBuffer(nil)
}

url := c.Address() + path

req, reqErr := http.NewRequestWithContext(ctx, httpMethod, url, body)
if reqErr != nil {
return nil, reqErr
}

var client = c.newHTTPClientWithHeaders(map[string]string{
"Authorization": "Bearer " + c.Token(),
"Content-Type": "application/json",
"Content-Type": contentType,
})
return client.Do(req)
}
Expand Down
221 changes: 221 additions & 0 deletions api/packages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
package api

import (
"archive/zip"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"os"

"github.com/shurcooL/graphql"
)

// Packages is a API client for working with Humio packages.
type Packages struct {
client *Client
}

// Packages constructs a Packages API client.
func (c *Client) Packages() *Packages { return &Packages{client: c} }

// ValidationResponse contain the results of a package validation.
type ValidationResponse struct {
InstallationErrors []string `json:"installationErrors"`
ParseErrors []string `json:"parseErrors"`
}

// IsValid returns true if there are no errors in the package
func (resp *ValidationResponse) IsValid() bool {
return (len(resp.InstallationErrors) == 0) && (len(resp.ParseErrors) == 0)
}

// Validate checks a package declaration validity against a Humio
// server.
func (p *Packages) Validate(repoOrViewName string, absDiretoryPath string) (*ValidationResponse, error) {
zipFilePath, err := createTempZipFromFolder(absDiretoryPath)

if err != nil {
return nil, err
}

urlPath := "api/v1/packages/analyze?view=" + url.QueryEscape(repoOrViewName)

fileReader, openErr := os.Open(zipFilePath)

if openErr != nil {
return nil, openErr
}
defer fileReader.Close()

response, httpErr := p.client.HTTPRequestContext(context.Background(), "POST", urlPath, fileReader, "application/zip")

if httpErr != nil {
return nil, httpErr
}

if response.StatusCode >= 400 {
return nil, fmt.Errorf("Bad response. %s", response.Status)
}

var report ValidationResponse
decoder := json.NewDecoder(response.Body)
decodeErr := decoder.Decode(&report)

if decodeErr != nil {
return nil, decodeErr
}

return &report, nil
}

// InstallArchive installs a local package (zip file).
func (p *Packages) InstallArchive(repoOrViewName string, pathToZip string) error {

fileReader, openErr := os.Open(pathToZip)

if openErr != nil {
return openErr
}
defer fileReader.Close()

urlPath := "api/v1/packages/install?view=" + url.QueryEscape(repoOrViewName)

response, httpErr := p.client.HTTPRequestContext(context.Background(), "POST", urlPath, fileReader, "application/zip")

if httpErr != nil {
return httpErr
}

if response.StatusCode >= 400 {
return fmt.Errorf("Bad response. %s", response.Status)
}

return nil
}

type (
// VersionedPackageSpecifier is the ID and version of a package, e.g foo/[email protected]
VersionedPackageSpecifier string
)

// UninstallPackage uninstalls a package by name.
func (p *Packages) UninstallPackage(repoOrViewName string, packageID string) error {

var m struct {
StartDataRedistribution struct {
// We have to make a selection, so just take __typename
Typename graphql.String `graphql:"__typename"`
} `graphql:"uninstallPackage(packageId: $packageId, viewName: $viewName)"`
}

variables := map[string]interface{}{
"packageId": VersionedPackageSpecifier(packageID),
"viewName": graphql.String(repoOrViewName),
}

graphqlErr := p.client.Mutate(&m, variables)

return graphqlErr
}

// CreateArchive creates a archive by bundling the files in packageDirPath in a zip file.
func (p *Packages) CreateArchive(packageDirPath string, targetFileName string) error {

outFile, err := os.Create(targetFileName)

if err != nil {
return err
}
defer outFile.Close()

return createZipFromFolder(packageDirPath, outFile)
}

// InstallFromDirectory installs a package from a directory containing the package files.
func (p *Packages) InstallFromDirectory(packageDirPath string, targetRepoOrView string) error {
zipFilePath, err := createTempZipFromFolder(packageDirPath)

if err != nil {
return err
}

zipFile, err := os.Open(zipFilePath)

if err != nil {
return err
}

defer zipFile.Close()
defer os.Remove(zipFile.Name())

if err != nil {
return err
}

return p.InstallArchive(targetRepoOrView, zipFilePath)
}

func createTempZipFromFolder(baseFolder string) (string, error) {
tempDir := os.TempDir()
zipFile, err := ioutil.TempFile(tempDir, "humio-package.*.zip")

if err != nil {
return "", err
}

err = createZipFromFolder(baseFolder, zipFile)

if err != nil {
return "", err
}

return zipFile.Name(), nil
}

func createZipFromFolder(baseFolder string, outFile *os.File) error {
// Create a new zip archive.
w := zip.NewWriter(outFile)

// Add some files to the archive.
addFiles(w, baseFolder, "")

// Make sure to check the error on Close.
err := w.Close()
if err != nil {
return err
}
return nil
}

func addFiles(w *zip.Writer, basePath, baseInZip string) {
// Open the Directory
files, err := ioutil.ReadDir(basePath)
if err != nil {
fmt.Println(err)
}

for _, file := range files {
if !file.IsDir() {
dat, err := ioutil.ReadFile(basePath + file.Name())
if err != nil {
fmt.Println(err)
}

// Add some files to the archive.
f, err := w.Create(baseInZip + file.Name())
if err != nil {
fmt.Println(err)
}
_, err = f.Write(dat)
if err != nil {
fmt.Println(err)
}
} else if file.IsDir() {
// Drill down
newBase := basePath + file.Name() + "/"
addFiles(w, newBase, baseInZip+file.Name()+"/")
}
}
}
4 changes: 2 additions & 2 deletions api/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func (q *QueryJobs) Poll(repository string, id string) (QueryResult, error) {
}

func (q *QueryJobs) PollContext(ctx context.Context, repository string, id string) (QueryResult, error) {
resp, err := q.client.HTTPRequestContext(ctx, http.MethodGet, "api/v1/repositories/"+url.QueryEscape(repository)+"/queryjobs/"+id, bytes.NewBuffer(nil))
resp, err := q.client.HTTPRequestContext(ctx, http.MethodGet, "api/v1/repositories/"+url.QueryEscape(repository)+"/queryjobs/"+id, nil, JSONContentType)

if err != nil {
return QueryResult{}, err
Expand All @@ -119,6 +119,6 @@ func (q *QueryJobs) PollContext(ctx context.Context, repository string, id strin
}

func (q *QueryJobs) Delete(repository string, id string) error {
_, err := q.client.HTTPRequest(http.MethodDelete, "api/v1/repositories/"+url.QueryEscape(repository)+"/queryjobs/"+id, bytes.NewBuffer(nil))
_, err := q.client.HTTPRequest(http.MethodDelete, "api/v1/repositories/"+url.QueryEscape(repository)+"/queryjobs/"+id, nil)
return err
}
33 changes: 33 additions & 0 deletions cmd/humioctl/packages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright © 2020 Humio Ltd.
//
// 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 main

import (
"github.com/spf13/cobra"
)

func newPackagesCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "packages",
Short: "[BETA] Manage packages",
}

cmd.AddCommand(validatePackageCmd())
cmd.AddCommand(archivePackageCmd())
cmd.AddCommand(installPackageCmd())
cmd.AddCommand(uninstallPackageCmd())

return cmd
}
61 changes: 61 additions & 0 deletions cmd/humioctl/packages_archive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright © 2018 Humio Ltd.
//
// 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 main

import (
"fmt"
"os"
"path/filepath"

"github.com/humio/cli/prompt"

"github.com/spf13/cobra"
)

func archivePackageCmd() *cobra.Command {
cmd := cobra.Command{
Use: "archive [flags] <package-dir> <output-file>",
Short: " Create a zip containing the content of a package directory.",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
out := prompt.NewPrompt(cmd.OutOrStdout())
dirPath := args[0]
outPath := args[1]

if !filepath.IsAbs(dirPath) {
var err error
dirPath, err = filepath.Abs(dirPath)
if err != nil {
out.Error(fmt.Sprintf("Invalid path: %s", err))
os.Exit(1)
}
dirPath += "/"
}

out.Info(fmt.Sprintf("Archiving Package in: %s", dirPath))

// Get the HTTP client
client := NewApiClient(cmd)

createErr := client.Packages().CreateArchive(dirPath, outPath)
if createErr != nil {
out.Error(fmt.Sprintf("Errors creating archive: %s", createErr))
os.Exit(1)
}
},
}

return &cmd
}
Loading

0 comments on commit c179e99

Please sign in to comment.