<commit_before>// Copyright (c) 2015 Pagoda Box Inc
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v.
// 2.0. If a copy of the MPL was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
//

package commands

//
import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/spf13/cobra"

	api "github.com/pagodabox/nanobox-api-client"
	"github.com/pagodabox/nanobox-cli/auth"
	"github.com/pagodabox/nanobox-cli/config"
	"github.com/pagodabox/nanobox-cli/util"
	"github.com/pagodabox/nanobox-golang-stylish"
)

var tw *tar.Writer

//
var publishCmd = &cobra.Command{
	Use:   "publish",
	Short: "Publishes an engine to nanobox.io",
	Long: `
Description:
  Publishes an engine to nanobox.io`,

	Run: nanoPublish,
}

// nanoPublish
func nanoPublish(ccmd *cobra.Command, args []string) {
	//
	stylish.Header("publishing engine")

	//
	api.UserSlug, api.AuthToken = auth.Authenticate()

	// create a new release
	fmt.Printf(stylish.Bullet("Creating release..."))
	release := &api.EngineReleaseCreateOptions{}

	//
	if _, err := os.Stat("./Enginefile"); err != nil {
		fmt.Println("Enginefile not found. Be sure to publish from a project directory. Exiting... ")
		os.Exit(1)
	}

	if err := config.ParseConfig("./Enginefile", release); err != nil {
		fmt.Printf("Nanobox failed to parse your Enginefile. Please ensure it is valid YAML and try again.\n")
		os.Exit(1)
	}

	fmt.Printf(stylish.Bullet("Verifying engine is publishable..."))

	// determine if any required fields (name, version, language, summary) are missing,
	// if any are found to be missing exit 1
	// NOTE: I do this using fallthrough for asthetics onlye. The message is generic
	// enough that all cases will return the same message, and this looks better than
	// a single giant case (var == "" || var == "" || ...)
	switch {
	case release.Language == "":
		fallthrough
	case release.Name == "":
		fallthrough
	case release.Summary == "":
		fallthrough
	case release.Version == "":
		fmt.Printf(stylish.Error("required fields missing", `Your Enginefile is missing one or more of the following required fields for publishing:

  name:      # the name of your project
  version:   # the current version of the project
  language:  # the lanauge (ruby, golang, etc.) of the engine
  summary:   # a 140 character summary of the project

Please ensure all required fields are provided and try again.`))

		os.Exit(1)
	}

	// attempt to read a README.md file and add it to the release...
	b, err := ioutil.ReadFile("./README.md")
	if err != nil {

		// this only fails if the file is not found, EOF is not an error. If no Readme
		// is found exit 1
		fmt.Printf(stylish.Error("missing readme", "Your engine is missing a README.md file. This file is required for publishing, as it is the only way for you to communicate how to use your engine. Please add a README.md and try again."))
		os.Exit(1)
	}

	//
	release.Readme = string(b)

	// this is our predefined list of everything that gets archived as part of the
	// engine being published
	files := []string{
		"./bin",
		// "./lib",
		// "./templates",
		// "./files",
		"./Enginefile",
	}

	// check to ensure no required files are missing
	for _, f := range files {
		if fi, _ := os.Stat(f); fi == nil {
			fmt.Printf(stylish.Error("required files missing", "Your Engine is missing one or more required files for publishing. Please read the following documentation to ensure all required files are included and try again.:\n\ndocs.nanobox.io/engines/project-creation/#example-engine-file-structure\n"))
			os.Exit(1)
		}
	}

	//
	// GET to API to see if engine exists
	fmt.Printf(stylish.Bullet("Checking for existing engine on nanobox.io"))
	if _, err := api.GetEngine(api.UserSlug, release.Name); err != nil {

		// if no engine is found create one
		if apiErr, _ := err.(api.APIError); apiErr.Code == 404 {

			fmt.Printf(stylish.SubTaskStart("Creating new engine on nanobox.io"))

			//
			engineCreateOptions := &api.EngineCreateOptions{Name: release.Name}
			if _, err := api.CreateEngine(engineCreateOptions); err != nil {
				util.LogFatal("[commands.publish] api.CreateEngine() failed", err)
			}

			// wait until engine has been successfuly created before uploading to s3
			for {
				fmt.Print(".")

				p, err := api.GetEngine(api.UserSlug, release.Name)
				if err != nil {
					util.LogFatal("[commands.publish] api.GetEngine() failed", err)
				}

				// once the engine is "active", break
				if p.State == "active" {
					break
				}

				//
				time.Sleep(1000 * time.Millisecond)
			}

			// generically handle any other errors
		} else {
			util.LogFatal("[commands publish] api.GetEngine failed", err)
		}

		stylish.Success()
	}

	// once the whole thing is working again, try swaping the go routine to be on
	// readers instead of the writer. the writer will block until readers are done
	// reading, so there may not be a need for the wait groups.

	// write the archive to a local file
	// archive, err := os.Create(fmt.Sprintf("%v-%v.release.tgz", release.Name, release.Version))
	// if err != nil {
	// 	util.LogFatal("[commands.publish] os.Create() failed", err)
	// }
	// defer archive.Close()

	// create an empty buffer for writing the file contents to for the subsequent
	// upload
	archive := bytes.NewBuffer(nil)

	//
	h := md5.New()

	//
	mw := io.MultiWriter(h, archive)

	//
	gzw := gzip.NewWriter(mw)

	//
	tw = tar.NewWriter(gzw)

	//
	wg := &sync.WaitGroup{}
	wg.Add(1)

	//
	go func() {

		defer gzw.Close()
		defer tw.Close()

		// if not required files are missing, tarball the engine for publishing
		for _, f := range files {
			if err := filepath.Walk(f, tarFile); err != nil {
				util.LogFatal("[commands.publish] filepath.Walk() failed", err)
			}
		}

		wg.Done()
	}()

	wg.Wait()

	// add the checksum for the new release once its finished being archived
	release.Checksum = fmt.Sprintf("%x", h.Sum(nil))

	//
	// attempt to upload the release to S3

	//
	fmt.Printf(stylish.Bullet("Uploading release to s3..."))

	v := url.Values{}
	v.Add("user_slug", api.UserSlug)
	v.Add("auth_token", api.AuthToken)
	v.Add("version", release.Version)

	//
	s3url, err := util.RequestS3URL(fmt.Sprintf("http://api.nanobox.io/v1/engines/%v/request_upload?%v", release.Name, v.Encode()))
	if err != nil {
		util.LogFatal("[commands/publish] util.RequestS3URL failed", err)
	}

	//
	if err := util.S3Upload(s3url, archive); err != nil {
		util.LogFatal("[commands/publish] util.S3Upload failed", err)
	}

	//
	// if the release uploaded successfully to s3, created one on odin
	fmt.Printf(stylish.Bullet("Uploading release to nanobox.io"))
	if _, err := api.CreateEngineRelease(release.Name, release); err != nil {
		util.LogFatal("[commands.publish] api.CreateEngineRelease() failed", err)
	}
}

// tarFile
func tarFile(path string, fi os.FileInfo, err error) error {

	// only want to tar files...
	if !fi.Mode().IsDir() {

		// fmt.Println("TARING!", path)

		// create header for this file
		header := &tar.Header{
			Name:    path,
			Size:    fi.Size(),
			Mode:    int64(fi.Mode()),
			ModTime: fi.ModTime(),
		}

		// write the header to the tarball archive
		if err := tw.WriteHeader(header); err != nil {
			return err
		}

		// open the file for taring...
		f, err := os.Open(path)
		if err != nil {
			return err
		}
		defer f.Close()

		// copy the file data to the tarball
		if _, err := io.Copy(tw, f); err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>only publish required and found files/folders<commit_after>// Copyright (c) 2015 Pagoda Box Inc
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v.
// 2.0. If a copy of the MPL was not distributed with this file, You can obtain one
// at http://mozilla.org/MPL/2.0/.
//

package commands

//
import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/spf13/cobra"

	api "github.com/pagodabox/nanobox-api-client"
	"github.com/pagodabox/nanobox-cli/auth"
	"github.com/pagodabox/nanobox-cli/config"
	"github.com/pagodabox/nanobox-cli/util"
	"github.com/pagodabox/nanobox-golang-stylish"
)

var tw *tar.Writer

//
var publishCmd = &cobra.Command{
	Use:   "publish",
	Short: "Publishes an engine to nanobox.io",
	Long: `
Description:
  Publishes an engine to nanobox.io`,

	Run: nanoPublish,
}

// nanoPublish
func nanoPublish(ccmd *cobra.Command, args []string) {
	//
	stylish.Header("publishing engine")

	//
	api.UserSlug, api.AuthToken = auth.Authenticate()

	// create a new release
	fmt.Printf(stylish.Bullet("Creating release..."))
	release := &api.EngineReleaseCreateOptions{}

	//
	if _, err := os.Stat("./Enginefile"); err != nil {
		fmt.Println("Enginefile not found. Be sure to publish from a project directory. Exiting... ")
		os.Exit(1)
	}

	if err := config.ParseConfig("./Enginefile", release); err != nil {
		fmt.Printf("Nanobox failed to parse your Enginefile. Please ensure it is valid YAML and try again.\n")
		os.Exit(1)
	}

	fmt.Printf(stylish.Bullet("Verifying engine is publishable..."))

	// determine if any required fields (name, version, language, summary) are missing,
	// if any are found to be missing exit 1
	// NOTE: I do this using fallthrough for asthetics onlye. The message is generic
	// enough that all cases will return the same message, and this looks better than
	// a single giant case (var == "" || var == "" || ...)
	switch {
	case release.Language == "":
		fallthrough
	case release.Name == "":
		fallthrough
	case release.Summary == "":
		fallthrough
	case release.Version == "":
		fmt.Printf(stylish.Error("required fields missing", `Your Enginefile is missing one or more of the following required fields for publishing:

  name:      # the name of your project
  version:   # the current version of the project
  language:  # the lanauge (ruby, golang, etc.) of the engine
  summary:   # a 140 character summary of the project

Please ensure all required fields are provided and try again.`))

		os.Exit(1)
	}

	// attempt to read a README.md file and add it to the release...
	b, err := ioutil.ReadFile("./README.md")
	if err != nil {

		// this only fails if the file is not found, EOF is not an error. If no Readme
		// is found exit 1
		fmt.Printf(stylish.Error("missing readme", "Your engine is missing a README.md file. This file is required for publishing, as it is the only way for you to communicate how to use your engine. Please add a README.md and try again."))
		os.Exit(1)
	}

	//
	release.Readme = string(b)

	// this is our predefined list of everything that gets archived as part of the
	// engine being published
	files := map[string][]string{
		"required": []string{"./bin", "./Enginefile"},
		"optional": []string{"./lib", "./templates", "./files"},
	}

	//
	for k, v := range files {
		if k == "required" {

			// check to ensure no required files are missing
			for _, f := range v {
				if fi, _ := os.Stat(f); fi == nil {
					fmt.Printf(stylish.Error("required files missing", "Your Engine is missing one or more required files for publishing. Please read the following documentation to ensure all required files are included and try again.:\n\ndocs.nanobox.io/engines/project-creation/#example-engine-file-structure\n"))
					os.Exit(1)
				}
			}
		}
	}

	//
	// GET to API to see if engine exists
	fmt.Printf(stylish.Bullet("Checking for existing engine on nanobox.io"))
	if _, err := api.GetEngine(api.UserSlug, release.Name); err != nil {

		// if no engine is found create one
		if apiErr, _ := err.(api.APIError); apiErr.Code == 404 {

			fmt.Printf(stylish.SubTaskStart("Creating new engine on nanobox.io"))

			//
			engineCreateOptions := &api.EngineCreateOptions{Name: release.Name}
			if _, err := api.CreateEngine(engineCreateOptions); err != nil {
				util.LogFatal("[commands.publish] api.CreateEngine() failed", err)
			}

			// wait until engine has been successfuly created before uploading to s3
			for {
				fmt.Print(".")

				p, err := api.GetEngine(api.UserSlug, release.Name)
				if err != nil {
					util.LogFatal("[commands.publish] api.GetEngine() failed", err)
				}

				// once the engine is "active", break
				if p.State == "active" {
					break
				}

				//
				time.Sleep(1000 * time.Millisecond)
			}

			// generically handle any other errors
		} else {
			util.LogFatal("[commands publish] api.GetEngine failed", err)
		}

		stylish.Success()
	}

	// once the whole thing is working again, try swaping the go routine to be on
	// readers instead of the writer. the writer will block until readers are done
	// reading, so there may not be a need for the wait groups.

	// write the archive to a local file
	// archive, err := os.Create(fmt.Sprintf("%v-%v.release.tgz", release.Name, release.Version))
	// if err != nil {
	// 	util.LogFatal("[commands.publish] os.Create() failed", err)
	// }
	// defer archive.Close()

	// create an empty buffer for writing the file contents to for the subsequent
	// upload
	archive := bytes.NewBuffer(nil)

	//
	h := md5.New()

	//
	mw := io.MultiWriter(h, archive)

	//
	gzw := gzip.NewWriter(mw)

	//
	tw = tar.NewWriter(gzw)

	//
	wg := &sync.WaitGroup{}
	wg.Add(1)

	//
	go func() {

		defer gzw.Close()
		defer tw.Close()

		// range over each file type...
		for _, v := range files {

			// range over each file for each type...
			for _, f := range v {

				// required files have alrady been checked, so skip any remaining (optional)
				// files/folders that arent here
				if fi, _ := os.Stat(f); fi == nil {
					continue
				}

				// tarball any remaining files/folders that are found
				if err := filepath.Walk(f, tarFile); err != nil {
					util.LogFatal("[commands.publish] filepath.Walk() failed", err)
				}
			}
		}

		wg.Done()
	}()

	wg.Wait()

	// add the checksum for the new release once its finished being archived
	release.Checksum = fmt.Sprintf("%x", h.Sum(nil))

	//
	// attempt to upload the release to S3

	//
	fmt.Printf(stylish.Bullet("Uploading release to s3..."))

	v := url.Values{}
	v.Add("user_slug", api.UserSlug)
	v.Add("auth_token", api.AuthToken)
	v.Add("version", release.Version)

	//
	s3url, err := util.RequestS3URL(fmt.Sprintf("http://api.nanobox.io/v1/engines/%v/request_upload?%v", release.Name, v.Encode()))
	if err != nil {
		util.LogFatal("[commands/publish] util.RequestS3URL failed", err)
	}

	//
	if err := util.S3Upload(s3url, archive); err != nil {
		util.LogFatal("[commands/publish] util.S3Upload failed", err)
	}

	//
	// if the release uploaded successfully to s3, created one on odin
	fmt.Printf(stylish.Bullet("Uploading release to nanobox.io"))
	if _, err := api.CreateEngineRelease(release.Name, release); err != nil {
		util.LogFatal("[commands.publish] api.CreateEngineRelease() failed", err)
	}
}

// tarFile
func tarFile(path string, fi os.FileInfo, err error) error {

	// only want to tar files...
	if !fi.Mode().IsDir() {

		// fmt.Println("TARING!", path)

		// create header for this file
		header := &tar.Header{
			Name:    path,
			Size:    fi.Size(),
			Mode:    int64(fi.Mode()),
			ModTime: fi.ModTime(),
		}

		// write the header to the tarball archive
		if err := tw.WriteHeader(header); err != nil {
			return err
		}

		// open the file for taring...
		f, err := os.Open(path)
		if err != nil {
			return err
		}
		defer f.Close()

		// copy the file data to the tarball
		if _, err := io.Copy(tw, f); err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package api

import (
	"errors"
	"net/http"
	"net/http/httptest"
	"reflect"
	"strings"
	"testing"

	conf "github.com/QubitProducts/bamboo/configuration"
	"github.com/QubitProducts/bamboo/services/service"
)

type testStorage struct {
	services         []service.Service
	err              error
	upsertChan       chan service.Service
	upsertResultChan chan error
	deleteChan       chan string
	deleteResultChan chan error
}

func newTestStorage(services []service.Service, err error) *testStorage {
	return &testStorage{
		services:         services,
		err:              err,
		upsertChan:       make(chan service.Service),
		upsertResultChan: make(chan error),
		deleteChan:       make(chan string),
		deleteResultChan: make(chan error),
	}
}

func (s *testStorage) All() ([]service.Service, error) {
	return s.services, s.err
}

func (s *testStorage) Upsert(service service.Service) error {
	s.upsertChan <- service
	return <-s.upsertResultChan
}

func (s *testStorage) Delete(serviceID string) error {
	s.deleteChan <- serviceID
	return <-s.deleteResultChan
}

func TestServiceAll(t *testing.T) {
	for _, test := range []struct {
		services []service.Service
		err      error
		status   int
		output   string
	}{
		{
			services: []service.Service{},
			err:      nil,
			status:   http.StatusOK,
			output:   "{}",
		},
		{
			services: []service.Service{},
			err:      errors.New("test error"),
			status:   http.StatusBadRequest,
			output:   "test error\n",
		},
		{
			services: []service.Service{
				service.Service{
					Id:     "/some/service",
					Acl:    "path_beg /some/service",
					Config: make(map[string]string),
				},
			},
			err:    nil,
			status: http.StatusOK,
			output: `{"/some/service":{"Id":"/some/service","Acl":"path_beg /some/service","Config":{}}}`,
		},
	} {
		c := &conf.Configuration{}
		store := newTestStorage(test.services, test.err)
		s := &ServiceAPI{
			Config:  c,
			Storage: store,
		}

		r, err := http.NewRequest("GET", "/api/services", nil)
		if err != nil {
			t.Fatalf("Error creating request: %s", err)
		}
		w := httptest.NewRecorder()

		s.All(w, r)

		if w.Code != test.status {
			t.Errorf("got %d, wanted %d", w.Code, test.status)
		}

		if w.Body.String() != test.output {
			t.Errorf("got '%s', wanted '%s'", w.Body.String(), test.output)
		}
	}
}

func TestServiceCreate(t *testing.T) {
	for _, test := range []struct {
		body     string
		expected *service.Service
		err      error
		status   int
		output   string
	}{
		{
			body:   "",
			status: http.StatusBadRequest,
			output: "Unable to decode JSON request\n",
		},
		{
			body:   `{}`,
			status: http.StatusBadRequest,
			output: "can not use empty ID\n",
		},
		{
			body: `{"Id":"/some/service","Acl":"path_beg /some/service"}`,
			expected: &service.Service{
				Id:     "/some/service",
				Acl:    "path_beg /some/service",
				Config: nil,
			},
			status: http.StatusOK,
			output: `{"Id":"/some/service","Acl":"path_beg /some/service","Config":null}`,
		},
		{
			body: `{"Id":"some/service","Acl":"path_beg /some/service"}`,
			expected: &service.Service{
				Id:     "/some/service",
				Acl:    "path_beg /some/service",
				Config: nil,
			},
			status: http.StatusOK,
			output: `{"Id":"/some/service","Acl":"path_beg /some/service","Config":null}`,
		},
		{
			body: `{"Id":"/some/service","Acl":"path_beg /some/service"}`,
			expected: &service.Service{
				Id:     "/some/service",
				Acl:    "path_beg /some/service",
				Config: nil,
			},
			err:    errors.New("test error"),
			status: http.StatusBadRequest,
			output: "test error\n",
		},
	} {
		c := &conf.Configuration{}
		store := newTestStorage([]service.Service{}, nil)
		s := &ServiceAPI{
			Config:  c,
			Storage: store,
		}

		r, err := http.NewRequest("POST", "/api/services", strings.NewReader(test.body))
		if err != nil {
			t.Fatalf("Error creating request: %s", err)
		}
		w := httptest.NewRecorder()

		go func() {
			service := <-store.upsertChan
			if !reflect.DeepEqual(service, *test.expected) {
				t.Errorf("got %#v, wanted %#v", service, test.expected)
			}
			store.upsertResultChan <- test.err
		}()

		s.Create(w, r)

		if w.Code != test.status {
			t.Errorf("got %d, wanted %d", w.Code, test.status)
		}

		if w.Body.String() != test.output {
			t.Errorf("got '%s', wanted '%s'", w.Body.String(), test.output)
		}
	}
}

func TestServicePut(t *testing.T) {
	for _, test := range []struct {
		body     string
		expected *service.Service
		err      error
		status   int
		output   string
	}{
		{
			body:   "",
			status: http.StatusBadRequest,
			output: "Unable to decode JSON request\n",
		},
		{
			body:   `{}`,
			status: http.StatusBadRequest,
			output: "can not use empty ID\n",
		},
		{
			body: `{"Id":"/some/service","Acl":"path_beg /some/service"}`,
			expected: &service.Service{
				Id:     "/some/service",
				Acl:    "path_beg /some/service",
				Config: nil,
			},
			status: http.StatusOK,
			output: `{"Id":"/some/service","Acl":"path_beg /some/service","Config":null}`,
		},
		{
			body: `{"Id":"some/service","Acl":"path_beg /some/service"}`,
			expected: &service.Service{
				Id:     "/some/service",
				Acl:    "path_beg /some/service",
				Config: nil,
			},
			status: http.StatusOK,
			output: `{"Id":"/some/service","Acl":"path_beg /some/service","Config":null}`,
		},
		{
			body: `{"Id":"/some/service","Acl":"path_beg /some/service"}`,
			expected: &service.Service{
				Id:     "/some/service",
				Acl:    "path_beg /some/service",
				Config: nil,
			},
			err:    errors.New("test error"),
			status: http.StatusBadRequest,
			output: "test error\n",
		},
	} {
		c := &conf.Configuration{}
		store := newTestStorage([]service.Service{}, nil)
		s := &ServiceAPI{
			Config:  c,
			Storage: store,
		}

		r, err := http.NewRequest("POST", "/api/services", strings.NewReader(test.body))
		if err != nil {
			t.Fatalf("Error creating request: %s", err)
		}
		w := httptest.NewRecorder()
		params := make(map[string]string)

		go func() {
			service := <-store.upsertChan
			if !reflect.DeepEqual(service, *test.expected) {
				t.Errorf("got %#v, wanted %#v", service, test.expected)
			}
			store.upsertResultChan <- test.err
		}()

		s.Put(params, w, r)

		if w.Code != test.status {
			t.Errorf("got %d, wanted %d", w.Code, test.status)
		}

		if w.Body.String() != test.output {
			t.Errorf("got '%s', wanted '%s'", w.Body.String(), test.output)
		}
	}
}

func TestServiceDelete(t *testing.T) {
	for _, test := range []struct {
		path     string
		expected string
		err      error
		status   int
		output   string
	}{
		{
			path:     "",
			expected: "",
			err:      nil,
			status:   http.StatusBadRequest,
			output:   "can not use empty ID\n",
		},
		{
			path:     "some/service",
			expected: "/some/service",
			err:      nil,
			status:   http.StatusOK,
			output:   "null",
		},
		{
			path:     "/some/service",
			expected: "/some/service",
			err:      nil,
			status:   http.StatusOK,
			output:   "null",
		},
		{
			path:     "/some/service",
			expected: "/some/service",
			err:      errors.New("test error"),
			status:   http.StatusBadRequest,
			output:   "test error\n",
		},
	} {
		c := &conf.Configuration{}
		store := newTestStorage([]service.Service{}, nil)
		s := &ServiceAPI{
			Config:  c,
			Storage: store,
		}

		r, err := http.NewRequest("POST", "/api/services/"+test.path, nil)
		if err != nil {
			t.Fatalf("Error creating request: %s", err)
		}
		w := httptest.NewRecorder()
		params := make(map[string]string)
		params["_1"] = test.path

		go func() {
			id := <-store.deleteChan
			if id != test.expected {
				t.Errorf("got '%s', wanted '%s'", id, test.expected)
			}
			store.deleteResultChan <- test.err
		}()

		s.Delete(params, w, r)

		if w.Code != test.status {
			t.Errorf("got %d, wanted %d", w.Code, test.status)
		}

		if w.Body.String() != test.output {
			t.Errorf("got '%s', wanted '%s'", w.Body.String(), test.output)
		}
	}
}
<commit_msg>Refactor duplicate tests.<commit_after>package api

import (
	"errors"
	"net/http"
	"net/http/httptest"
	"reflect"
	"strings"
	"testing"

	conf "github.com/QubitProducts/bamboo/configuration"
	"github.com/QubitProducts/bamboo/services/service"
)

type testStorage struct {
	services         []service.Service
	err              error
	upsertChan       chan service.Service
	upsertResultChan chan error
	deleteChan       chan string
	deleteResultChan chan error
}

func newTestStorage(services []service.Service, err error) *testStorage {
	return &testStorage{
		services:         services,
		err:              err,
		upsertChan:       make(chan service.Service),
		upsertResultChan: make(chan error),
		deleteChan:       make(chan string),
		deleteResultChan: make(chan error),
	}
}

func (s *testStorage) All() ([]service.Service, error) {
	return s.services, s.err
}

func (s *testStorage) Upsert(service service.Service) error {
	s.upsertChan <- service
	return <-s.upsertResultChan
}

func (s *testStorage) Delete(serviceID string) error {
	s.deleteChan <- serviceID
	return <-s.deleteResultChan
}

func TestServiceAll(t *testing.T) {
	for _, test := range []struct {
		services []service.Service
		err      error
		status   int
		output   string
	}{
		{
			services: []service.Service{},
			err:      nil,
			status:   http.StatusOK,
			output:   "{}",
		},
		{
			services: []service.Service{},
			err:      errors.New("test error"),
			status:   http.StatusBadRequest,
			output:   "test error\n",
		},
		{
			services: []service.Service{
				service.Service{
					Id:     "/some/service",
					Acl:    "path_beg /some/service",
					Config: make(map[string]string),
				},
			},
			err:    nil,
			status: http.StatusOK,
			output: `{"/some/service":{"Id":"/some/service","Acl":"path_beg /some/service","Config":{}}}`,
		},
	} {
		c := &conf.Configuration{}
		store := newTestStorage(test.services, test.err)
		s := &ServiceAPI{
			Config:  c,
			Storage: store,
		}

		r, err := http.NewRequest("GET", "/api/services", nil)
		if err != nil {
			t.Fatalf("Error creating request: %s", err)
		}
		w := httptest.NewRecorder()

		s.All(w, r)

		if w.Code != test.status {
			t.Errorf("got %d, wanted %d", w.Code, test.status)
		}

		if w.Body.String() != test.output {
			t.Errorf("got '%s', wanted '%s'", w.Body.String(), test.output)
		}
	}
}

type serviceUpdateFunc func(s *ServiceAPI, w http.ResponseWriter, r *http.Request)

func TestServiceUpdate(t *testing.T) {
	for _, call := range []serviceUpdateFunc{
		func(s *ServiceAPI, w http.ResponseWriter, r *http.Request) {
			s.Create(w, r)
		},
		func(s *ServiceAPI, w http.ResponseWriter, r *http.Request) {
			params := make(map[string]string)
			s.Put(params, w, r)
		},
	} {
		for _, test := range []struct {
			body     string
			expected *service.Service
			err      error
			status   int
			output   string
		}{
			{
				body:   "",
				status: http.StatusBadRequest,
				output: "Unable to decode JSON request\n",
			},
			{
				body:   `{}`,
				status: http.StatusBadRequest,
				output: "can not use empty ID\n",
			},
			{
				body: `{"Id":"/some/service","Acl":"path_beg /some/service"}`,
				expected: &service.Service{
					Id:     "/some/service",
					Acl:    "path_beg /some/service",
					Config: nil,
				},
				status: http.StatusOK,
				output: `{"Id":"/some/service","Acl":"path_beg /some/service","Config":null}`,
			},
			{
				body: `{"Id":"some/service","Acl":"path_beg /some/service"}`,
				expected: &service.Service{
					Id:     "/some/service",
					Acl:    "path_beg /some/service",
					Config: nil,
				},
				status: http.StatusOK,
				output: `{"Id":"/some/service","Acl":"path_beg /some/service","Config":null}`,
			},
			{
				body: `{"Id":"/some/service","Acl":"path_beg /some/service"}`,
				expected: &service.Service{
					Id:     "/some/service",
					Acl:    "path_beg /some/service",
					Config: nil,
				},
				err:    errors.New("test error"),
				status: http.StatusBadRequest,
				output: "test error\n",
			},
		} {
			c := &conf.Configuration{}
			store := newTestStorage([]service.Service{}, nil)
			s := &ServiceAPI{
				Config:  c,
				Storage: store,
			}

			r, err := http.NewRequest("POST", "/api/services", strings.NewReader(test.body))
			if err != nil {
				t.Fatalf("Error creating request: %s", err)
			}
			w := httptest.NewRecorder()

			join := make(chan bool)
			go func() {
				if test.expected != nil {
					service := <-store.upsertChan
					if !reflect.DeepEqual(service, *test.expected) {
						t.Errorf("got %#v, wanted %#v", service, test.expected)
					}
					store.upsertResultChan <- test.err
				}
				join <- true
			}()

			// call update function
			call(s, w, r)

			if w.Code != test.status {
				t.Errorf("got %d, wanted %d", w.Code, test.status)
			}

			if w.Body.String() != test.output {
				t.Errorf("got '%s', wanted '%s'", w.Body.String(), test.output)
			}
			<-join
		}
	}
}

func TestServiceDelete(t *testing.T) {
	for _, test := range []struct {
		path     string
		expected string
		err      error
		status   int
		output   string
	}{
		{
			path:     "",
			expected: "",
			err:      nil,
			status:   http.StatusBadRequest,
			output:   "can not use empty ID\n",
		},
		{
			path:     "some/service",
			expected: "/some/service",
			err:      nil,
			status:   http.StatusOK,
			output:   "null",
		},
		{
			path:     "/some/service",
			expected: "/some/service",
			err:      nil,
			status:   http.StatusOK,
			output:   "null",
		},
		{
			path:     "/some/service",
			expected: "/some/service",
			err:      errors.New("test error"),
			status:   http.StatusBadRequest,
			output:   "test error\n",
		},
	} {
		c := &conf.Configuration{}
		store := newTestStorage([]service.Service{}, nil)
		s := &ServiceAPI{
			Config:  c,
			Storage: store,
		}

		r, err := http.NewRequest("POST", "/api/services/"+test.path, nil)
		if err != nil {
			t.Fatalf("Error creating request: %s", err)
		}
		w := httptest.NewRecorder()
		params := make(map[string]string)
		params["_1"] = test.path

		go func() {
			id := <-store.deleteChan
			if id != test.expected {
				t.Errorf("got '%s', wanted '%s'", id, test.expected)
			}
			store.deleteResultChan <- test.err
		}()

		s.Delete(params, w, r)

		if w.Code != test.status {
			t.Errorf("got %d, wanted %d", w.Code, test.status)
		}

		if w.Body.String() != test.output {
			t.Errorf("got '%s', wanted '%s'", w.Body.String(), test.output)
		}
	}
}
<|endoftext|>


<commit_before>package commands

import (
	"os"
	"sort"
	"strconv"
	"time"

	"github.com/concourse/fly/rc"
	"github.com/concourse/fly/ui"
	"github.com/dgrijalva/jwt-go"
	"github.com/fatih/color"
)

type TargetsCommand struct{}

func (command *TargetsCommand) Execute([]string) error {
	flyYAML, err := rc.LoadTargets()
	if err != nil {
		return err
	}

	table := ui.Table{
		Headers: ui.TableRow{
			{Contents: "name", Color: color.New(color.Bold)},
			{Contents: "url", Color: color.New(color.Bold)},
			{Contents: "expiry", Color: color.New(color.Bold)},
		},
	}

	for targetName, targetValues := range flyYAML.Targets {
		expirationTime := GetExpirationFromString(targetValues.Token)

		row := ui.TableRow{
			{Contents: string(targetName)},
			{Contents: targetValues.API},
			{Contents: expirationTime},
		}

		table.Data = append(table.Data, row)
	}

	sort.Sort(table.Data)

	return table.Render(os.Stdout)
}

func GetExpirationFromString(token *rc.TargetToken) string {
	if token == nil {
		return "n/a"
	}

	parsedToken, _ := jwt.Parse(token.Value, func(token *jwt.Token) (interface{}, error) {
		return "", nil
	})

	var (
		ok       bool
		expClaim interface{}
	)
	if expClaim, ok = parsedToken.Claims["exp"]; !ok {
		return "n/a"
	}

	intSeconds, err := strconv.ParseInt(string(expClaim.(string)), 10, 64)
	if err != nil {
		return "n/a"
	}

	unixSeconds := time.Unix(intSeconds, 0)

	return unixSeconds.Format(time.RFC1123)
}
<commit_msg>Refactor ok check<commit_after>package commands

import (
	"os"
	"sort"
	"strconv"
	"time"

	"github.com/concourse/fly/rc"
	"github.com/concourse/fly/ui"
	"github.com/dgrijalva/jwt-go"
	"github.com/fatih/color"
)

type TargetsCommand struct{}

func (command *TargetsCommand) Execute([]string) error {
	flyYAML, err := rc.LoadTargets()
	if err != nil {
		return err
	}

	table := ui.Table{
		Headers: ui.TableRow{
			{Contents: "name", Color: color.New(color.Bold)},
			{Contents: "url", Color: color.New(color.Bold)},
			{Contents: "expiry", Color: color.New(color.Bold)},
		},
	}

	for targetName, targetValues := range flyYAML.Targets {
		expirationTime := GetExpirationFromString(targetValues.Token)

		row := ui.TableRow{
			{Contents: string(targetName)},
			{Contents: targetValues.API},
			{Contents: expirationTime},
		}

		table.Data = append(table.Data, row)
	}

	sort.Sort(table.Data)

	return table.Render(os.Stdout)
}

func GetExpirationFromString(token *rc.TargetToken) string {
	if token == nil {
		return "n/a"
	}

	parsedToken, _ := jwt.Parse(token.Value, func(token *jwt.Token) (interface{}, error) {
		return "", nil
	})

	expClaim, ok := parsedToken.Claims["exp"]
	if !ok {
		return "n/a"
	}

	intSeconds, err := strconv.ParseInt(string(expClaim.(string)), 10, 64)
	if err != nil {
		return "n/a"
	}

	unixSeconds := time.Unix(intSeconds, 0)

	return unixSeconds.Format(time.RFC1123)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"text/template"

	"github.com/krasoffski/goplts/gopl/ch04/xkcd"
)

// NAME is name of comic cache file.
const NAME = "comic.cache"

const templ = `{{ len .Comics }} comics
{{- $withT := .WithT}}
{{ range $key, $value := .Comics }}----------------------------------------
Num: {{ $value.Num }}
URL: {{ $value.URL }}
Title: {{ $value.SafeTitle }}
{{- if $withT }}
Transcript: {{ $value.Transcript }}
{{- end }}
{{ end }}`

var report = template.Must(template.New("comicslist").Parse(templ))

func printComics(comics map[int]*xkcd.Info, showTranscript bool) {
	err := report.Execute(os.Stdout, struct {
		Comics map[int]*xkcd.Info
		WithT  bool
	}{comics, showTranscript})

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

func initCache(cache *xkcd.Cache, force bool) error {

	if _, err := os.Stat(NAME); !force && err == nil {
		return fmt.Errorf("init error: cache file %s already exists", NAME)
	}

	// TODO: think about what perform first fetch or file create.
	if err := cache.Update(false); err != nil {
		return fmt.Errorf("update error: %s", err)
	}

	file, err := os.Create(NAME)
	if err != nil {
		return err
	}
	defer file.Close()

	buf := bufio.NewWriter(file)

	if err := cache.Dump(buf); err != nil {
		return fmt.Errorf("save error: %s", err)
	}
	return nil
}

func loadCache(cache *xkcd.Cache) {
	file, err := os.Open(NAME)
	if err != nil {
		log.Fatalln(err)
	}
	defer file.Close()

	err = cache.Load(file)

	if err == io.EOF {
		log.Fatalln("comic cache is empty")
	}

	if err != nil {
		log.Fatalln(err)
	}
}

func dumpCache(cache *xkcd.Cache) {
	file, err := os.Create(NAME)
	if err != nil {
		log.Fatalln(err)
	}
	defer file.Close()

	if err := cache.Dump(file); err != nil {
		log.Fatalln(err)
	}
}

// TODO: fix formting issue
func showCache(cache *xkcd.Cache, num int, showTranscript bool) {
	if num > 0 {
		val := cache.Comics[num]
		if val == nil {
			fmt.Printf("#%-4d NO SUCH COMIC IN CACHE\n", num)
			return
		}
		printComics(map[int]*xkcd.Info{num: val}, showTranscript)
	} else if num == 0 {
		printComics(cache.Comics, showTranscript)
	} else {
		log.Fatalf("error: negative comic num  %d is not allowed\n", num)
	}
}

func searchCache(cache *xkcd.Cache, ss []string, showTranscript bool) {
	if len(ss) == 0 {
		fmt.Println("empty search query")
		return
	}
	printComics(cache.Search(ss), showTranscript)
}

func statusCache(cache *xkcd.Cache) {
	fmt.Printf("Last comic: %d, cached at: %d-%02d-%02d %02d:%02d\n",
		cache.LastNum,
		cache.CheckedAt.Year(),
		cache.CheckedAt.Month(),
		cache.CheckedAt.Day(),
		cache.CheckedAt.Hour(),
		cache.CheckedAt.Minute())
}

func main() {
	initCmd := flag.NewFlagSet("init", flag.ExitOnError)
	syncCmd := flag.NewFlagSet("sync", flag.ExitOnError)
	showCmd := flag.NewFlagSet("show", flag.ExitOnError)
	statusCmd := flag.NewFlagSet("status", flag.ExitOnError)
	searchCmd := flag.NewFlagSet("search", flag.ExitOnError)

	initForcePtr := initCmd.Bool("force", false, "Force init with xkcd site.")
	syncForcePtr := syncCmd.Bool("force", false, "Force sync with xkcd site.")

	showNumPtr := showCmd.Int("num", 0, "Number of comic to show.")
	showTransPtr := showCmd.Bool("transcript", false, "Print info comic info with Transcript.")

	searchTransPtr := searchCmd.Bool("transcript", false, "Print info comic info with Transcript.")

	if len(os.Args) < 2 {
		fmt.Println("init|sync|status|show|search subcommand is required")
		os.Exit(1)
	}
	switch os.Args[1] {
	case "init":
		initCmd.Parse(os.Args[2:])
	case "sync":
		syncCmd.Parse(os.Args[2:])
	case "show":
		showCmd.Parse(os.Args[2:])
	case "status":
		statusCmd.Parse(os.Args[2:])
	case "search":
		searchCmd.Parse(os.Args[2:])
	default:
		fmt.Println("init|sync|show|status|search subcommand is required")
		os.Exit(1)
	}

	cache := xkcd.NewCache()

	if initCmd.Parsed() {
		if err := initCache(cache, *initForcePtr); err != nil {
			log.Fatalln(err)
		}
	}

	if syncCmd.Parsed() {
		loadCache(cache)
		cache.Update(*syncForcePtr)
		dumpCache(cache)
	}
	if showCmd.Parsed() {
		loadCache(cache)
		showCache(cache, *showNumPtr, *showTransPtr)
	}
	if searchCmd.Parsed() {
		loadCache(cache)
		searchCache(cache, searchCmd.Args(), *searchTransPtr)
	}
	if statusCmd.Parsed() {
		loadCache(cache)
		statusCache(cache)
	}

}
<commit_msg>[4.12] Unified error handling for cli.<commit_after>package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"text/template"

	"github.com/krasoffski/goplts/gopl/ch04/xkcd"
)

// NAME is name of comic cache file.
const NAME = "comic.cache"

const templ = `{{ len .Comics }} comic(s)
{{- $withT := .WithT}}
{{ range $key, $value := .Comics }}----------------------------------------
Num: {{ $value.Num }}
URL: {{ $value.URL }}
Title: {{ $value.SafeTitle }}
{{- if $withT }}
Transcript: {{ $value.Transcript }}
{{- end }}
{{ end }}`

var report = template.Must(template.New("comicslist").Parse(templ))

func printfErrAndExit(format string, a ...interface{}) {
	fmt.Fprintf(os.Stderr, format, a...)
	os.Exit(1)
}

func printComics(comics map[int]*xkcd.Info, showTranscript bool) {
	err := report.Execute(os.Stdout, struct {
		Comics map[int]*xkcd.Info
		WithT  bool
	}{comics, showTranscript})

	if err != nil {
		printfErrAndExit("print cache error: %s\n", err)
	}
}

func initCache(cache *xkcd.Cache, force bool) {

	if _, err := os.Stat(NAME); !force && err == nil {
		printfErrAndExit("init cache error: cache file %s already exists\n", NAME)
	}

	// TODO: think about what perform first fetch or file create.
	if err := cache.Update(false); err != nil {
		printfErrAndExit("init cache error: %s\n", err)
	}

	dumpCache(cache)
}

func loadCache(cache *xkcd.Cache) {
	file, err := os.Open(NAME)
	if err != nil {
		printfErrAndExit("load cache error: %s\n", err)
	}
	defer file.Close()

	err = cache.Load(file)

	if err == io.EOF {
		printfErrAndExit("load cache error: cache is empty\n")
	}

	if err != nil {
		log.Fatalln(err)
	}
}

func dumpCache(cache *xkcd.Cache) {
	file, err := os.Create(NAME)
	if err != nil {
		printfErrAndExit("file cache error: %s\n", err)
	}
	defer file.Close()
	buf := bufio.NewWriter(file)
	if err := cache.Dump(buf); err != nil {
		printfErrAndExit("save cache error: %s\n", err)
	}
}

func showCache(cache *xkcd.Cache, num int, showTranscript bool) {
	if num > 0 {
		val := cache.Comics[num]
		if val == nil {
			printfErrAndExit("show cache error: no such comic %d\n", num)
		}
		printComics(map[int]*xkcd.Info{num: val}, showTranscript)
	} else if num == 0 {
		printComics(cache.Comics, showTranscript)
	} else {
		printfErrAndExit("show cache error: invalid num %d\n", num)
	}
}

func searchCache(cache *xkcd.Cache, ss []string, showTranscript bool) {
	if len(ss) == 0 {
		printfErrAndExit("search cache error: empty query\n")
	}
	printComics(cache.Search(ss), showTranscript)
}

func statusCache(cache *xkcd.Cache) {
	fmt.Printf("last comic: %d, cached at: %d-%02d-%02d %02d:%02d\n",
		cache.LastNum,
		cache.CheckedAt.Year(),
		cache.CheckedAt.Month(),
		cache.CheckedAt.Day(),
		cache.CheckedAt.Hour(),
		cache.CheckedAt.Minute())
}

func main() {
	initCmd := flag.NewFlagSet("init", flag.ExitOnError)
	syncCmd := flag.NewFlagSet("sync", flag.ExitOnError)
	showCmd := flag.NewFlagSet("show", flag.ExitOnError)
	statusCmd := flag.NewFlagSet("status", flag.ExitOnError)
	searchCmd := flag.NewFlagSet("search", flag.ExitOnError)

	initForcePtr := initCmd.Bool("force", false, "Force init with xkcd site.")
	syncForcePtr := syncCmd.Bool("force", false, "Force sync with xkcd site.")

	showNumPtr := showCmd.Int("num", 0, "Number of comic to show.")
	showTransPtr := showCmd.Bool("transcript", false, "Print info comic info with Transcript.")

	searchTransPtr := searchCmd.Bool("transcript", false, "Print info comic info with Transcript.")

	if len(os.Args) < 2 {
		fmt.Println("init|sync|status|show|search subcommand is required")
		os.Exit(1)
	}
	switch os.Args[1] {
	case "init":
		initCmd.Parse(os.Args[2:])
	case "sync":
		syncCmd.Parse(os.Args[2:])
	case "show":
		showCmd.Parse(os.Args[2:])
	case "status":
		statusCmd.Parse(os.Args[2:])
	case "search":
		searchCmd.Parse(os.Args[2:])
	default:
		printfErrAndExit("init|sync|show|status|search subcommand is required\n")
	}

	cache := xkcd.NewCache()

	if initCmd.Parsed() {
		initCache(cache, *initForcePtr)
	}

	loadCache(cache)

	if syncCmd.Parsed() {
		cache.Update(*syncForcePtr)
		dumpCache(cache)
	}

	if showCmd.Parsed() {
		showCache(cache, *showNumPtr, *showTransPtr)
	}

	if searchCmd.Parsed() {
		searchCache(cache, searchCmd.Args(), *searchTransPtr)
	}

	if statusCmd.Parsed() {
		statusCache(cache)
	}

}
<|endoftext|>


<commit_before>package backends

import (
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/bndw/pick/errors"
	"github.com/mitchellh/go-homedir"
)

const (
	defaultSafeFileMode     = 0600
	defaultSafeFileName     = "pick.safe"
	defaultSafeDirMode      = 0700
	defaultSafeDirName      = ".pick"
	defaultBackupDir        = "%s/%s/backups"
	defaultBackupFileName   = "pick_%s.safe"
	defaultBackupTimeFormat = "2006-01-02_15-04-05"
)

var (
	safePath string
	homeDir  string
)

type DiskBackend struct {
	path         string
	backupConfig backupConfig
}

type fileInfoSlice []os.FileInfo

func NewDiskBackend(config Config) (*DiskBackend, error) {
	var err error
	if homeDir, err = homedir.Dir(); err != nil {
		return nil, err
	}

	safePath, ok := config.Settings["path"].(string)
	if ok {
		safePath = formatHomeDir(safePath, homeDir)
	} else {
		safePath, err = defaultSafePath()
		if err != nil {
			return nil, err
		}
	}

	config.Backup.DirPath = fmt.Sprintf(defaultBackupDir, homeDir, defaultSafeDirName)

	return &DiskBackend{
		path:         safePath,
		backupConfig: config.Backup,
	}, nil
}

func (db *DiskBackend) Load() ([]byte, error) {
	if _, err := os.Stat(db.path); err != nil {
		if os.IsNotExist(err) {
			return nil, &errors.SafeNotFound{}
		} else {
			return nil, err
		}
	}

	return ioutil.ReadFile(db.path)
}

func (db *DiskBackend) Save(data []byte) error {
	tmpFile := db.path + ".tmp"
	if err := ioutil.WriteFile(tmpFile, data, defaultSafeFileMode); err != nil {
		os.Remove(tmpFile)
		return err
	}
	if err := os.Rename(tmpFile, db.path); err != nil {
		os.Remove(tmpFile)
		return err
	}
	return nil
}

func (f fileInfoSlice) Len() int {
	return len(f)
}

func (f fileInfoSlice) Less(i, j int) bool {
	return f[i].ModTime().Before(f[j].ModTime())
}

func (f fileInfoSlice) Swap(i, j int) {
	f[i], f[j] = f[j], f[i]
}

func (db *DiskBackend) cleanOldBackups(max int) error {
	files, err := ioutil.ReadDir(db.backupConfig.DirPath)
	if err != nil {
		return err
	}

	filesSorted := make(fileInfoSlice, 0, len(files))
	for _, f := range files {
		filesSorted = append(filesSorted, f)
	}
	sort.Sort(filesSorted)
	max = min(max, len(filesSorted))

	for _, f := range filesSorted[:len(filesSorted)-max] {
		p := fmt.Sprintf("%s/%s", db.backupConfig.DirPath, f.Name())
		if err := os.Remove(p); err != nil {
			fmt.Println("Error removing old backup", err.Error())
		}
	}

	return nil
}

func min(a, b int) int {
	if a <= b {
		return a
	}
	return b
}

func (db *DiskBackend) Backup() error {
	if db.backupConfig.MaxFiles == 0 {
		// Keep no backups
		db.cleanOldBackups(0)
		return &errors.BackupDisabled{}
	} else if db.backupConfig.MaxFiles > 0 {
		// Subtract one as we are about to create another backup
		if err := db.cleanOldBackups(db.backupConfig.MaxFiles - 1); err != nil {
			fmt.Println("Failed to remove old backup(s)", err.Error())
		}
	}

	backupDir := db.backupConfig.DirPath
	timeFormat := time.Now().Format(defaultBackupTimeFormat)
	backupFileName := fmt.Sprintf(defaultBackupFileName, timeFormat)
	backupPath := backupDir + "/" + backupFileName

	if _, err := os.Stat(backupDir); err != nil {
		if os.IsNotExist(err) {
			if mkerr := os.Mkdir(backupDir, defaultSafeDirMode); mkerr != nil {
				return mkerr
			}
		} else {
			return err
		}
	}

	if _, err := os.Stat(backupPath); err == nil {
		return &errors.BackupFileExists{}
	}

	data, err := db.Load()
	if err != nil {
		return err
	}
	return ioutil.WriteFile(backupPath, data, defaultSafeFileMode)
}

func defaultSafePath() (string, error) {
	safeDir := fmt.Sprintf("%s/%s", homeDir, defaultSafeDirName)

	if _, err := os.Stat(safeDir); err != nil {
		if os.IsNotExist(err) {
			if mkerr := os.Mkdir(safeDir, defaultSafeDirMode); mkerr != nil {
				return "", mkerr
			}
		} else {
			return "", err
		}
	}

	safePath := fmt.Sprintf("%s/%s", safeDir, defaultSafeFileName)

	return safePath, nil
}

func formatHomeDir(str, home string) string {
	return strings.Replace(str, "$HOME", home, 1)
}
<commit_msg>Do not show an error message if automatic backup removal failed due to a missing backup folder<commit_after>package backends

import (
	"fmt"
	"io/ioutil"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/bndw/pick/errors"
	"github.com/mitchellh/go-homedir"
)

const (
	defaultSafeFileMode     = 0600
	defaultSafeFileName     = "pick.safe"
	defaultSafeDirMode      = 0700
	defaultSafeDirName      = ".pick"
	defaultBackupDir        = "%s/%s/backups"
	defaultBackupFileName   = "pick_%s.safe"
	defaultBackupTimeFormat = "2006-01-02_15-04-05"
)

var (
	safePath string
	homeDir  string
)

type DiskBackend struct {
	path         string
	backupConfig backupConfig
}

type fileInfoSlice []os.FileInfo

func NewDiskBackend(config Config) (*DiskBackend, error) {
	var err error
	if homeDir, err = homedir.Dir(); err != nil {
		return nil, err
	}

	safePath, ok := config.Settings["path"].(string)
	if ok {
		safePath = formatHomeDir(safePath, homeDir)
	} else {
		safePath, err = defaultSafePath()
		if err != nil {
			return nil, err
		}
	}

	config.Backup.DirPath = fmt.Sprintf(defaultBackupDir, homeDir, defaultSafeDirName)

	return &DiskBackend{
		path:         safePath,
		backupConfig: config.Backup,
	}, nil
}

func (db *DiskBackend) Load() ([]byte, error) {
	if _, err := os.Stat(db.path); err != nil {
		if os.IsNotExist(err) {
			return nil, &errors.SafeNotFound{}
		} else {
			return nil, err
		}
	}

	return ioutil.ReadFile(db.path)
}

func (db *DiskBackend) Save(data []byte) error {
	tmpFile := db.path + ".tmp"
	if err := ioutil.WriteFile(tmpFile, data, defaultSafeFileMode); err != nil {
		os.Remove(tmpFile)
		return err
	}
	if err := os.Rename(tmpFile, db.path); err != nil {
		os.Remove(tmpFile)
		return err
	}
	return nil
}

func (f fileInfoSlice) Len() int {
	return len(f)
}

func (f fileInfoSlice) Less(i, j int) bool {
	return f[i].ModTime().Before(f[j].ModTime())
}

func (f fileInfoSlice) Swap(i, j int) {
	f[i], f[j] = f[j], f[i]
}

func (db *DiskBackend) cleanOldBackups(max int) error {
	files, err := ioutil.ReadDir(db.backupConfig.DirPath)
	if err != nil {
		return err
	}

	filesSorted := make(fileInfoSlice, 0, len(files))
	for _, f := range files {
		filesSorted = append(filesSorted, f)
	}
	sort.Sort(filesSorted)
	max = min(max, len(filesSorted))

	for _, f := range filesSorted[:len(filesSorted)-max] {
		p := fmt.Sprintf("%s/%s", db.backupConfig.DirPath, f.Name())
		if err := os.Remove(p); err != nil {
			fmt.Println("Error removing old backup", err.Error())
		}
	}

	return nil
}

func min(a, b int) int {
	if a <= b {
		return a
	}
	return b
}

func (db *DiskBackend) Backup() error {
	if db.backupConfig.MaxFiles == 0 {
		// Keep no backups
		db.cleanOldBackups(0)
		return &errors.BackupDisabled{}
	} else if db.backupConfig.MaxFiles > 0 {
		// Subtract one as we are about to create another backup
		if err := db.cleanOldBackups(db.backupConfig.MaxFiles - 1); err != nil {
			if !os.IsNotExist(err) {
				fmt.Println("Failed to remove old backup(s)", err.Error())
			}
		}
	}

	backupDir := db.backupConfig.DirPath
	timeFormat := time.Now().Format(defaultBackupTimeFormat)
	backupFileName := fmt.Sprintf(defaultBackupFileName, timeFormat)
	backupPath := backupDir + "/" + backupFileName

	if _, err := os.Stat(backupDir); err != nil {
		if os.IsNotExist(err) {
			if mkerr := os.Mkdir(backupDir, defaultSafeDirMode); mkerr != nil {
				return mkerr
			}
		} else {
			return err
		}
	}

	if _, err := os.Stat(backupPath); err == nil {
		return &errors.BackupFileExists{}
	}

	data, err := db.Load()
	if err != nil {
		return err
	}
	return ioutil.WriteFile(backupPath, data, defaultSafeFileMode)
}

func defaultSafePath() (string, error) {
	safeDir := fmt.Sprintf("%s/%s", homeDir, defaultSafeDirName)

	if _, err := os.Stat(safeDir); err != nil {
		if os.IsNotExist(err) {
			if mkerr := os.Mkdir(safeDir, defaultSafeDirMode); mkerr != nil {
				return "", mkerr
			}
		} else {
			return "", err
		}
	}

	safePath := fmt.Sprintf("%s/%s", safeDir, defaultSafeFileName)

	return safePath, nil
}

func formatHomeDir(str, home string) string {
	return strings.Replace(str, "$HOME", home, 1)
}
<|endoftext|>


<commit_before>package backuptar

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/Microsoft/go-winio"
	"github.com/Microsoft/go-winio/archive/tar" // until archive/tar supports pax extensions in its interface
)

const (
	c_ISUID  = 04000   // Set uid
	c_ISGID  = 02000   // Set gid
	c_ISVTX  = 01000   // Save text (sticky bit)
	c_ISDIR  = 040000  // Directory
	c_ISFIFO = 010000  // FIFO
	c_ISREG  = 0100000 // Regular file
	c_ISLNK  = 0120000 // Symbolic link
	c_ISBLK  = 060000  // Block special file
	c_ISCHR  = 020000  // Character special file
	c_ISSOCK = 0140000 // Socket
)

const (
	hdrFileAttributes     = "fileattr"
	hdrSecurityDescriptor = "sd"
	hdrMountPoint         = "mountpoint"
)

func writeZeroes(w io.Writer, count int64) error {
	buf := make([]byte, 8192)
	c := len(buf)
	for i := int64(0); i < count; i += int64(c) {
		if int64(c) > count-i {
			c = int(count - i)
		}
		_, err := w.Write(buf[:c])
		if err != nil {
			return err
		}
	}
	return nil
}

func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
	curOffset := int64(0)
	for {
		bhdr, err := br.Next()
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		if err != nil {
			return err
		}
		if bhdr.Id != winio.BackupSparseBlock {
			return fmt.Errorf("unexpected stream %d", bhdr.Id)
		}

		// archive/tar does not support writing sparse files
		// so just write zeroes to catch up to the current offset.
		err = writeZeroes(t, bhdr.Offset-curOffset)
		if bhdr.Size == 0 {
			break
		}
		n, err := io.Copy(t, br)
		if err != nil {
			return err
		}
		curOffset = bhdr.Offset + n
	}
	return nil
}

// BasicInfoHeader creates a tar header from basic file information.
func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header {
	hdr := &tar.Header{
		Name:         filepath.ToSlash(name),
		Size:         size,
		Typeflag:     tar.TypeReg,
		ModTime:      time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()),
		ChangeTime:   time.Unix(0, fileInfo.ChangeTime.Nanoseconds()),
		AccessTime:   time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()),
		CreationTime: time.Unix(0, fileInfo.CreationTime.Nanoseconds()),
		Winheaders:   make(map[string]string),
	}
	hdr.Winheaders[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes)

	if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
		hdr.Mode |= c_ISDIR
		hdr.Size = 0
		hdr.Typeflag = tar.TypeDir
	}
	return hdr
}

// WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream.
//
// This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS.
//
// The additional Win32 metadata is:
//
// MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value
//
// MSWINDOWS.sd: The Win32 security descriptor, in SDDL (string) format
//
// MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink)
func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error {
	name = filepath.ToSlash(name)
	hdr := BasicInfoHeader(name, size, fileInfo)
	br := winio.NewBackupStreamReader(r)
	var dataHdr *winio.BackupHeader
	for dataHdr == nil {
		bhdr, err := br.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		switch bhdr.Id {
		case winio.BackupData:
			hdr.Mode |= c_ISREG
			dataHdr = bhdr
		case winio.BackupSecurity:
			sd, err := ioutil.ReadAll(br)
			if err != nil {
				return err
			}
			sddl, err := winio.SecurityDescriptorToSddl(sd)
			if err != nil {
				return err
			}
			hdr.Winheaders[hdrSecurityDescriptor] = sddl

		case winio.BackupReparseData:
			hdr.Mode |= c_ISLNK
			hdr.Typeflag = tar.TypeSymlink
			reparseBuffer, err := ioutil.ReadAll(br)
			rp, err := winio.DecodeReparsePoint(reparseBuffer)
			if err != nil {
				return err
			}
			if rp.IsMountPoint {
				hdr.Winheaders[hdrMountPoint] = "1"
			}
			hdr.Linkname = rp.Target
		case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
			// ignore these streams
		default:
			return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id)
		}
	}

	err := t.WriteHeader(hdr)
	if err != nil {
		return err
	}

	if dataHdr != nil {
		// A data stream was found. Copy the data.
		if (dataHdr.Attributes & winio.StreamSparseAttributes) == 0 {
			if size != dataHdr.Size {
				return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size)
			}
			_, err = io.Copy(t, br)
			if err != nil {
				return err
			}
		} else {
			err = copySparse(t, br)
			if err != nil {
				return err
			}
		}
	}

	// Look for streams after the data stream. The only ones we handle are alternate data streams.
	// Other streams may have metadata that could be serialized, but the tar header has already
	// been written. In practice, this means that we don't get EA or TXF metadata.
	for {
		bhdr, err := br.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		switch bhdr.Id {
		case winio.BackupAlternateData:
			altName := bhdr.Name
			if strings.HasSuffix(altName, ":$DATA") {
				altName = altName[:len(altName)-len(":$DATA")]
			}
			if (bhdr.Attributes & winio.StreamSparseAttributes) == 0 {
				hdr = &tar.Header{
					Name:       name + altName,
					Mode:       hdr.Mode,
					Typeflag:   tar.TypeReg,
					Size:       bhdr.Size,
					ModTime:    hdr.ModTime,
					AccessTime: hdr.AccessTime,
					ChangeTime: hdr.ChangeTime,
				}
				err = t.WriteHeader(hdr)
				if err != nil {
					return err
				}
				_, err = io.Copy(t, br)
				if err != nil {
					return err
				}

			} else {
				// Unsupported for now, since the size of the alternate stream is not present
				// in the backup stream until after the data has been read.
				return errors.New("tar of sparse alternate data streams is unsupported")
			}
		case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
			// ignore these streams
		default:
			return fmt.Errorf("%s: unknown stream ID %d after data", name, bhdr.Id)
		}
	}
	return nil
}

// FileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by
// WriteTarFileFromBackupStream.
func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) {
	name = hdr.Name
	if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
		size = hdr.Size
	}
	fileInfo = &winio.FileBasicInfo{
		LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()),
		LastWriteTime:  syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
		ChangeTime:     syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()),
		CreationTime:   syscall.NsecToFiletime(hdr.CreationTime.UnixNano()),
	}
	if attrStr, ok := hdr.Winheaders[hdrFileAttributes]; ok {
		attr, err := strconv.ParseUint(attrStr, 10, 32)
		if err != nil {
			return "", 0, nil, err
		}
		fileInfo.FileAttributes = uintptr(attr)
	} else {
		if hdr.Typeflag == tar.TypeDir {
			fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
		}
	}
	return
}

// WriteBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple
// tar file entries in order to collect all the alternate data streams for the file, it returns the next
// tar file that was not processed, or io.EOF is there are no more.
func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
	bw := winio.NewBackupStreamWriter(w)
	if sddl, ok := hdr.Winheaders[hdrSecurityDescriptor]; ok {
		sd, err := winio.SddlToSecurityDescriptor(sddl)
		if err != nil {
			return nil, err
		}
		bhdr := winio.BackupHeader{
			Id:   winio.BackupSecurity,
			Size: int64(len(sd)),
		}
		err = bw.WriteHeader(&bhdr)
		if err != nil {
			return nil, err
		}
		_, err = bw.Write(sd)
		if err != nil {
			return nil, err
		}
	}
	if hdr.Typeflag == tar.TypeSymlink {
		_, isMountPoint := hdr.Winheaders[hdrMountPoint]
		rp := winio.ReparsePoint{
			Target:       filepath.FromSlash(hdr.Linkname),
			IsMountPoint: isMountPoint,
		}
		reparse := winio.EncodeReparsePoint(&rp)
		bhdr := winio.BackupHeader{
			Id:   winio.BackupReparseData,
			Size: int64(len(reparse)),
		}
		err := bw.WriteHeader(&bhdr)
		if err != nil {
			return nil, err
		}
		_, err = bw.Write(reparse)
		if err != nil {
			return nil, err
		}
	}
	if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
		bhdr := winio.BackupHeader{
			Id:   winio.BackupData,
			Size: hdr.Size,
		}
		err := bw.WriteHeader(&bhdr)
		if err != nil {
			return nil, err
		}
		_, err = io.Copy(bw, t)
		if err != nil {
			return nil, err
		}
	}
	// Copy all the alternate data streams and return the next non-ADS header.
	for {
		ahdr, err := t.Next()
		if err != nil {
			return nil, err
		}
		if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
			return ahdr, nil
		}
		bhdr := winio.BackupHeader{
			Id:   winio.BackupAlternateData,
			Size: ahdr.Size,
			Name: ahdr.Name[len(hdr.Name)+1:] + ":$DATA",
		}
		err = bw.WriteHeader(&bhdr)
		if err != nil {
			return nil, err
		}
		_, err = io.Copy(bw, t)
		if err != nil {
			return nil, err
		}
	}
}
<commit_msg>Remove use of SDDL in tar headers<commit_after>package backuptar

import (
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/Microsoft/go-winio"
	"github.com/Microsoft/go-winio/archive/tar" // until archive/tar supports pax extensions in its interface
)

const (
	c_ISUID  = 04000   // Set uid
	c_ISGID  = 02000   // Set gid
	c_ISVTX  = 01000   // Save text (sticky bit)
	c_ISDIR  = 040000  // Directory
	c_ISFIFO = 010000  // FIFO
	c_ISREG  = 0100000 // Regular file
	c_ISLNK  = 0120000 // Symbolic link
	c_ISBLK  = 060000  // Block special file
	c_ISCHR  = 020000  // Character special file
	c_ISSOCK = 0140000 // Socket
)

const (
	hdrFileAttributes        = "fileattr"
	hdrSecurityDescriptor    = "sd"
	hdrRawSecurityDescriptor = "rawsd"
	hdrMountPoint            = "mountpoint"
)

func writeZeroes(w io.Writer, count int64) error {
	buf := make([]byte, 8192)
	c := len(buf)
	for i := int64(0); i < count; i += int64(c) {
		if int64(c) > count-i {
			c = int(count - i)
		}
		_, err := w.Write(buf[:c])
		if err != nil {
			return err
		}
	}
	return nil
}

func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
	curOffset := int64(0)
	for {
		bhdr, err := br.Next()
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
		if err != nil {
			return err
		}
		if bhdr.Id != winio.BackupSparseBlock {
			return fmt.Errorf("unexpected stream %d", bhdr.Id)
		}

		// archive/tar does not support writing sparse files
		// so just write zeroes to catch up to the current offset.
		err = writeZeroes(t, bhdr.Offset-curOffset)
		if bhdr.Size == 0 {
			break
		}
		n, err := io.Copy(t, br)
		if err != nil {
			return err
		}
		curOffset = bhdr.Offset + n
	}
	return nil
}

// BasicInfoHeader creates a tar header from basic file information.
func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header {
	hdr := &tar.Header{
		Name:         filepath.ToSlash(name),
		Size:         size,
		Typeflag:     tar.TypeReg,
		ModTime:      time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()),
		ChangeTime:   time.Unix(0, fileInfo.ChangeTime.Nanoseconds()),
		AccessTime:   time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()),
		CreationTime: time.Unix(0, fileInfo.CreationTime.Nanoseconds()),
		Winheaders:   make(map[string]string),
	}
	hdr.Winheaders[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes)

	if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
		hdr.Mode |= c_ISDIR
		hdr.Size = 0
		hdr.Typeflag = tar.TypeDir
	}
	return hdr
}

// WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream.
//
// This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS.
//
// The additional Win32 metadata is:
//
// MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value
//
// MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format
//
// MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink)
func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error {
	name = filepath.ToSlash(name)
	hdr := BasicInfoHeader(name, size, fileInfo)
	br := winio.NewBackupStreamReader(r)
	var dataHdr *winio.BackupHeader
	for dataHdr == nil {
		bhdr, err := br.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		switch bhdr.Id {
		case winio.BackupData:
			hdr.Mode |= c_ISREG
			dataHdr = bhdr
		case winio.BackupSecurity:
			sd, err := ioutil.ReadAll(br)
			if err != nil {
				return err
			}
			hdr.Winheaders[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd)

		case winio.BackupReparseData:
			hdr.Mode |= c_ISLNK
			hdr.Typeflag = tar.TypeSymlink
			reparseBuffer, err := ioutil.ReadAll(br)
			rp, err := winio.DecodeReparsePoint(reparseBuffer)
			if err != nil {
				return err
			}
			if rp.IsMountPoint {
				hdr.Winheaders[hdrMountPoint] = "1"
			}
			hdr.Linkname = rp.Target
		case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
			// ignore these streams
		default:
			return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id)
		}
	}

	err := t.WriteHeader(hdr)
	if err != nil {
		return err
	}

	if dataHdr != nil {
		// A data stream was found. Copy the data.
		if (dataHdr.Attributes & winio.StreamSparseAttributes) == 0 {
			if size != dataHdr.Size {
				return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size)
			}
			_, err = io.Copy(t, br)
			if err != nil {
				return err
			}
		} else {
			err = copySparse(t, br)
			if err != nil {
				return err
			}
		}
	}

	// Look for streams after the data stream. The only ones we handle are alternate data streams.
	// Other streams may have metadata that could be serialized, but the tar header has already
	// been written. In practice, this means that we don't get EA or TXF metadata.
	for {
		bhdr, err := br.Next()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		switch bhdr.Id {
		case winio.BackupAlternateData:
			altName := bhdr.Name
			if strings.HasSuffix(altName, ":$DATA") {
				altName = altName[:len(altName)-len(":$DATA")]
			}
			if (bhdr.Attributes & winio.StreamSparseAttributes) == 0 {
				hdr = &tar.Header{
					Name:       name + altName,
					Mode:       hdr.Mode,
					Typeflag:   tar.TypeReg,
					Size:       bhdr.Size,
					ModTime:    hdr.ModTime,
					AccessTime: hdr.AccessTime,
					ChangeTime: hdr.ChangeTime,
				}
				err = t.WriteHeader(hdr)
				if err != nil {
					return err
				}
				_, err = io.Copy(t, br)
				if err != nil {
					return err
				}

			} else {
				// Unsupported for now, since the size of the alternate stream is not present
				// in the backup stream until after the data has been read.
				return errors.New("tar of sparse alternate data streams is unsupported")
			}
		case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
			// ignore these streams
		default:
			return fmt.Errorf("%s: unknown stream ID %d after data", name, bhdr.Id)
		}
	}
	return nil
}

// FileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by
// WriteTarFileFromBackupStream.
func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) {
	name = hdr.Name
	if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
		size = hdr.Size
	}
	fileInfo = &winio.FileBasicInfo{
		LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()),
		LastWriteTime:  syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
		ChangeTime:     syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()),
		CreationTime:   syscall.NsecToFiletime(hdr.CreationTime.UnixNano()),
	}
	if attrStr, ok := hdr.Winheaders[hdrFileAttributes]; ok {
		attr, err := strconv.ParseUint(attrStr, 10, 32)
		if err != nil {
			return "", 0, nil, err
		}
		fileInfo.FileAttributes = uintptr(attr)
	} else {
		if hdr.Typeflag == tar.TypeDir {
			fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
		}
	}
	return
}

// WriteBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple
// tar file entries in order to collect all the alternate data streams for the file, it returns the next
// tar file that was not processed, or io.EOF is there are no more.
func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
	bw := winio.NewBackupStreamWriter(w)
	var sd []byte
	var err error
	// Maintaining old SDDL-based behavior for backward compatibility.  All new tar headers written
	// by this library will have raw binary for the security descriptor.
	if sddl, ok := hdr.Winheaders[hdrSecurityDescriptor]; ok {
		sd, err = winio.SddlToSecurityDescriptor(sddl)
		if err != nil {
			return nil, err
		}
	}
	if sdraw, ok := hdr.Winheaders[hdrRawSecurityDescriptor]; ok {
		sd, err = base64.StdEncoding.DecodeString(sdraw)
		if err != nil {
			return nil, err
		}
	}
	if len(sd) != 0 {
		bhdr := winio.BackupHeader{
			Id:   winio.BackupSecurity,
			Size: int64(len(sd)),
		}
		err := bw.WriteHeader(&bhdr)
		if err != nil {
			return nil, err
		}
		_, err = bw.Write(sd)
		if err != nil {
			return nil, err
		}
	}
	if hdr.Typeflag == tar.TypeSymlink {
		_, isMountPoint := hdr.Winheaders[hdrMountPoint]
		rp := winio.ReparsePoint{
			Target:       filepath.FromSlash(hdr.Linkname),
			IsMountPoint: isMountPoint,
		}
		reparse := winio.EncodeReparsePoint(&rp)
		bhdr := winio.BackupHeader{
			Id:   winio.BackupReparseData,
			Size: int64(len(reparse)),
		}
		err := bw.WriteHeader(&bhdr)
		if err != nil {
			return nil, err
		}
		_, err = bw.Write(reparse)
		if err != nil {
			return nil, err
		}
	}
	if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
		bhdr := winio.BackupHeader{
			Id:   winio.BackupData,
			Size: hdr.Size,
		}
		err := bw.WriteHeader(&bhdr)
		if err != nil {
			return nil, err
		}
		_, err = io.Copy(bw, t)
		if err != nil {
			return nil, err
		}
	}
	// Copy all the alternate data streams and return the next non-ADS header.
	for {
		ahdr, err := t.Next()
		if err != nil {
			return nil, err
		}
		if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
			return ahdr, nil
		}
		bhdr := winio.BackupHeader{
			Id:   winio.BackupAlternateData,
			Size: ahdr.Size,
			Name: ahdr.Name[len(hdr.Name)+1:] + ":$DATA",
		}
		err = bw.WriteHeader(&bhdr)
		if err != nil {
			return nil, err
		}
		_, err = io.Copy(bw, t)
		if err != nil {
			return nil, err
		}
	}
}
<|endoftext|>


<commit_before>package junos

// To View the entire configuration, use the keyword "full" for the first
// argument. If anything else outside of "full" is specified, it will return
// the configuration of the specified top-level stanza only. So "security"
// would return everything under the "security" stanza.
func ExampleJunos_viewConfiguration() {
	// Establish our session first.
	jnpr, err := junos.NewSession(host, user, password)
	if err != nil {
		log.Fatal(err)
	}
	defer jnpr.Close()

	// Output format can be "text" or "xml".
	config, err := jnpr.GetConfig("full", "text")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(config)
}

// Comparing and working with rollback configurations.
func ExampleJunos_rollbackConfigurations() {
	// Establish our session first.
	jnpr, err := junos.NewSession(host, user, password)
	if err != nil {
		log.Fatal(err)
	}
	defer jnpr.Close()

	// If you want to view the difference between the current configuration and a rollback
	// one, then you can use the ConfigDiff() function to specify a previous config:
	diff, err := jnpr.ConfigDiff(3)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(diff)

	// You can rollback to a previous state, or the rescue configuration by using
	// the RollbackConfig() function:
	err := jnpr.RollbackConfig(3)
	if err != nil {
		fmt.Println(err)
	}

	// Create a rescue config from the active configuration.
	jnpr.Rescue("save")

	// You can also delete a rescue config.
	jnpr.Rescue("delete")

	// Rollback to the "rescue" configuration.
	err := jnpr.RollbackConfig("rescue")
	if err != nil {
		fmt.Println(err)
	}
}

// Configuring devices.
func ExampleJunos_configuringDevices() {
	// Use the LoadConfig() function to load the configuration from a file.

	// When configuring a device, it is good practice to lock the configuration database,
	// load the config, commit the configuration, and then unlock the configuration database.
	// You can do this with the following functions: Lock(), Commit(), Unlock().

	// Multiple ways to commit a configuration.

	// Commit the configuration as normal.
	Commit()

	// Check the configuration for any syntax errors (NOTE: you must still issue a
	// Commit() afterwards).
	CommitCheck()

	// Commit at a later time, i.e. 4:30 PM.
	CommitAt("16:30:00")

	// Rollback configuration if a Commit() is not issued within the given <minutes>.
	CommitConfirm(15)

	// You can configure the Junos device by uploading a local file, or pulling from an
	// FTP/HTTP server. The LoadConfig() function takes three arguments:

	// filename or URL, format, and a boolean (true/false) "commit-on-load".

	// If you specify a URL, it must be in the following format:

	// ftp://<username>:<password>@hostname/pathname/file-name
	// http://<username>:<password>@hostname/pathname/file-name

	// Note: The default value for the FTP path variable is the user’s home directory. Thus,
	// by default the file path to the configuration file is relative to the user directory.
	// To specify an absolute path when using FTP, start the path with the characters %2F;
	// for example: ftp://username:password@hostname/%2Fpath/filename.

	// The format of the commands within the file must be one of the following types:

	// set
	// system name-server 1.1.1.1

	// text
	// system {
	//     name-server 1.1.1.1;
	// }

	// xml
	// <system>
	//     <name-server>
	//         <name>1.1.1.1</name>
	//     </name-server>
	// </system>

	// Establish our session first.
	jnpr, err := junos.NewSession(host, user, password)
	if err != nil {
		log.Fatal(err)
	}
	defer jnpr.Close()

	// If the third option is "true" then after the configuration is loaded, a commit
	// will be issued. If set to "false," you will have to commit the configuration
	// using one of the Commit() functions.
	jnpr.Lock()
	err := jnpr.LoadConfig("path-to-file.txt", "set", true)
	if err != nil {
		fmt.Println(err)
	}
	jnpr.Unlock()
}

// Running operational mode commands on a device.
func ExampleJunos_runCommands() {
	// Establish our session first.
	jnpr, err := junos.NewSession(host, user, password)
	if err != nil {
		log.Fatal(err)
	}
	defer jnpr.Close()

	// You can run operational mode commands such as "show" and "request" by using the
	// Command() function. Output formats can be "text" or "xml".

	// Results returned in text format.
	txtOutput, err := jnpr.Command("show chassis hardware", "text")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(txtOutput)

	// Results returned in XML format.
	xmlOutput, err := jnpr.Command("show chassis hardware", "xml")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(xmlOutput)
}

// Viewing basic information about the device.
func ExampleJunos_deviceInformation() {
	// Establish our session first.
	jnpr, err := junos.NewSession(host, user, password)
	if err != nil {
		log.Fatal(err)
	}
	defer jnpr.Close()

	// When you call the PrintFacts() function, it just prints out the platform
	// and software information to the console.
	jnpr.PrintFacts()

	// You can also loop over the struct field that contains this information yourself:
	fmt.Printf("Hostname: %s", jnpr.Hostname)
	for _, data := range jnpr.Platform {
		fmt.Printf("Model: %s, Version: %s", data.Model, data.Version)
	}
	// Output: Model: SRX240H2, Version: 12.1X47-D10.4
}

// Establishing a connection to Junos Space and working with devices.
func ExampleJunosSpace_devices() {
	// Establish a connection to a Junos Space server.
	space := junos.NewServer("space.company.com", "admin", "juniper123")

	// Get the list of devices.
	devices, err := space.Devices()
	if err != nil {
		fmt.Println(err)
	}

	// Iterate over our device list and display some information about them.
	for _, device := range devices.Devices {
		fmt.Printf("Name: %s, IP Address: %s, Platform: %s\n", device.Name, device.IP, device.Platform)
	}

	// Add a device to Junos Space.
	jobID, err = space.AddDevice("sdubs-fw", "admin", "juniper123")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(jobID)
	// Output: 1345283

	// Remove a device from Junos Space.
	err = space.RemoveDevice("sdubs-fw")
	if err != nil {
		fmt.Println(err)
	}
}

// Software upgrades using Junos Space.
func ExampleJunosSpace_softwareUpgrade() {
	// Establish a connection to a Junos Space server.
	space := junos.NewServer("space.company.com", "admin", "juniper123")

	// Staging software on a device. The last parameter is whether or not to remove any
	// existing images from the device; boolean.
	//
	// This will not upgrade the device, but only place the image there to be used at a later
	// time.
	jobID, err := space.StageSoftware("sdubs-fw", "junos-srxsme-12.1X46-D30.2-domestic.tgz", false)
	if err != nil {
		fmt.Println(err)
	}

	// If you want to issue a software upgrade to the device, here's how:

	// Configure our options, such as whether or not to reboot the device, etc.
	options := &junos.SoftwareUpgrade{
		UseDownloaded: true,
		Validate:      false,
		Reboot:        false,
		RebootAfter:   0,
		Cleanup:       false,
		RemoveAfter:   false,
	}

	jobID, err := space.DeploySoftware("sdubs-fw", "junos-srxsme-12.1X46-D30.2-domestic.tgz", options)
	if err != nil {
		fmt.Println(err)
	}

	// Remove a staged image from the device.
	jobID, err := space.RemoveStagedSoftware("sdubs-fw", "junos-srxsme-12.1X46-D30.2-domestic.tgz")
	if err != nil {
		fmt.Println(err)
	}
}

// Viewing information about Security Director devices (SRX, J-series, etc.).
func ExampleJunosSpace_securityDirectorDevices() {
	// Establish a connection to a Junos Space server.
	space := junos.NewServer("space.company.com", "admin", "juniper123")

	// List all security devices:
	devices, err := space.SecurityDevices()
	if err != nil {
		fmt.Println(err)
	}

	for _, device := range devices.Devices {
		fmt.Printf("%+v\n", device)
	}
}

// Working with address and service objects.
func ExampleJunosSpace_addressObjects() {
	// Establish a connection to a Junos Space server.
	space := junos.NewServer("space.company.com", "admin", "juniper123")

	// To view the address and service objects, you use the Addresses() and Services() functions. Both of them
	// take a "filter" parameter, which lets you search for objects matching your filter.

	//If you leave the parameter blank (e.g. ""), or specify "all", then every object is returned.

	// Address objects
	addresses, err := space.Addresses("all")
	if err != nil {
		fmt.Println(err)
	}

	for _, address := range addresses.Addresses {
		fmt.Printf("%+v\n", address)
	}

	// Service objects
	services, err := space.Services("all")
	if err != nil {
		fmt.Println(err)
	}

	for _, service := range services.Services {
		fmt.Printf("%+v\n", service)
	}

	// Add an address group. "true" as the first parameter means that we assume the
	// group is going to be an address group.
	space.AddGroup(true, "Blacklist-IPs", "Blacklisted IP addresses")

	// Add a service group. We do this by specifying "false" as the first parameter.
	space.AddGroup(false, "Web-Protocols", "All web-based protocols and ports")

	// Add an address object
	space.AddAddress("my-laptop", "2.2.2.2", "My personal laptop")

	// Add a network
	space.AddAddress("corporate-users", "192.168.1.0/24", "People on campus")

	// Add a service object with an 1800 second inactivity timeout (using "0" disables this feature)
	space.AddService("udp", "udp-5000", 5000, 5000, "UDP port 5000", 1800)

	// Add a service object with a port range
	space.AddService("tcp", "high-port-range", 40000, 65000, "TCP high ports", 0)

	// If you want to modify an existing object group, you do this with the ModifyObject() function. The
	// first parameter is whether the object is an address group (true) or a service group (false).

	// Add a service to a group
	space.ModifyObject(false, "add", "service-group", "service-name")

	// Remove an address object from a group
	space.ModifyObject(true, "remove", "Whitelisted-Addresses", "bad-ip")

	// Rename an object
	space.ModifyObject(false, "rename", "Web-Services", "Web-Ports")

	// Delete an object
	space.ModifyObject(true, "delete", "my-laptop")
}

// Working with polymorphic (variable) objects.
func ExampleJunosSpace_variables() {
	// Establish a connection to a Junos Space server.
	space := junos.NewServer("space.company.com", "admin", "juniper123")

	// Add a variable
	// The parameters are as follows: variable-name, description, default-value
	space.AddVariable("test-variable", "Our test variable", "default-object")

	// Create our session state for modifying variables
	v, err := space.ModifyVariable()
	if err != nil {
		log.Fatal(err)
	}

	// Adding objects to the variable
	v.Add("test-variable", "srx-1", "user-pc")
	v.Add("test-variable", "corp-firewall", "db-server")

	// Delete a variable
	space.DeleteVariable("test-variable")
}

// Working with policies.
func ExampleJunosSpace_policies() {
	// Establish a connection to a Junos Space server.
	space := junos.NewServer("space.company.com", "admin", "juniper123")

	// List all security policies Junos Space manages:
	policies, err := space.Policies()
	if err != nil {
		fmt.Println(err)
	}

	for _, policy := range policies.Policies {
		fmt.Printf("%s\n", policy.Name)
	}

	// For example, say we have been adding and removing objects in a group, and that group
	// is referenced in a firewall policy. Here's how to update the policy:

	// Update the policy. If "false" is specified, then the policy is only published, and the
	// device is not updated.
	job, err := space.PublishPolicy("Internet-Firewall-Policy", true)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("Job ID: %d\n", job)

	// Let's update a device knowing that we have some previously published services.
	job, err := space.UpdateDevice("firewall-1.company.com")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("Job ID: %d\n", job)
}
<commit_msg>Updated example<commit_after>package junos

// To View the entire configuration, use the keyword "full" for the first
// argument. If anything else outside of "full" is specified, it will return
// the configuration of the specified top-level stanza only. So "security"
// would return everything under the "security" stanza.
func ExampleJunos_viewConfiguration() {
	// Establish our session first.
	jnpr, err := junos.NewSession(host, user, password)
	if err != nil {
		log.Fatal(err)
	}
	defer jnpr.Close()

	// Output format can be "text" or "xml".
	config, err := jnpr.GetConfig("full", "text")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(config)
}

// Comparing and working with rollback configurations.
func ExampleJunos_rollbackConfigurations() {
	// Establish our session first.
	jnpr, err := junos.NewSession(host, user, password)
	if err != nil {
		log.Fatal(err)
	}
	defer jnpr.Close()

	// If you want to view the difference between the current configuration and a rollback
	// one, then you can use the ConfigDiff() function to specify a previous config:
	diff, err := jnpr.ConfigDiff(3)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(diff)

	// You can rollback to a previous state, or the rescue configuration by using
	// the RollbackConfig() function:
	err := jnpr.RollbackConfig(3)
	if err != nil {
		fmt.Println(err)
	}

	// Create a rescue config from the active configuration.
	jnpr.Rescue("save")

	// You can also delete a rescue config.
	jnpr.Rescue("delete")

	// Rollback to the "rescue" configuration.
	err := jnpr.RollbackConfig("rescue")
	if err != nil {
		fmt.Println(err)
	}
}

// Configuring devices.
func ExampleJunos_configuringDevices() {
	// Use the LoadConfig() function to load the configuration from a file.

	// When configuring a device, it is good practice to lock the configuration database,
	// load the config, commit the configuration, and then unlock the configuration database.
	// You can do this with the following functions: Lock(), Commit(), Unlock().

	// Multiple ways to commit a configuration.

	// Commit the configuration as normal.
	Commit()

	// Check the configuration for any syntax errors (NOTE: you must still issue a
	// Commit() afterwards).
	CommitCheck()

	// Commit at a later time, i.e. 4:30 PM.
	CommitAt("16:30:00")

	// Rollback configuration if a Commit() is not issued within the given <minutes>.
	CommitConfirm(15)

	// You can configure the Junos device by uploading a local file, or pulling from an
	// FTP/HTTP server. The LoadConfig() function takes three arguments:

	// filename or URL, format, and a boolean (true/false) "commit-on-load".

	// If you specify a URL, it must be in the following format:

	// ftp://<username>:<password>@hostname/pathname/file-name
	// http://<username>:<password>@hostname/pathname/file-name

	// Note: The default value for the FTP path variable is the user’s home directory. Thus,
	// by default the file path to the configuration file is relative to the user directory.
	// To specify an absolute path when using FTP, start the path with the characters %2F;
	// for example: ftp://username:password@hostname/%2Fpath/filename.

	// The format of the commands within the file must be one of the following types:

	// set
	// system name-server 1.1.1.1

	// text
	// system {
	//     name-server 1.1.1.1;
	// }

	// xml
	// <system>
	//     <name-server>
	//         <name>1.1.1.1</name>
	//     </name-server>
	// </system>

	// Establish our session first.
	jnpr, err := junos.NewSession(host, user, password)
	if err != nil {
		log.Fatal(err)
	}
	defer jnpr.Close()

	// If the third option is "true" then after the configuration is loaded, a commit
	// will be issued. If set to "false," you will have to commit the configuration
	// using one of the Commit() functions.
	jnpr.Lock()
	err := jnpr.LoadConfig("path-to-file.txt", "set", true)
	if err != nil {
		fmt.Println(err)
	}
	jnpr.Unlock()
}

// Running operational mode commands on a device.
func ExampleJunos_runningCommands() {
	// Establish our session first.
	jnpr, err := junos.NewSession(host, user, password)
	if err != nil {
		log.Fatal(err)
	}
	defer jnpr.Close()

	// You can run operational mode commands such as "show" and "request" by using the
	// Command() function. Output formats can be "text" or "xml".

	// Results returned in text format.
	txtOutput, err := jnpr.Command("show chassis hardware", "text")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(txtOutput)

	// Results returned in XML format.
	xmlOutput, err := jnpr.Command("show chassis hardware", "xml")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(xmlOutput)
}

// Viewing basic information about the device.
func ExampleJunos_deviceInformation() {
	// Establish our session first.
	jnpr, err := junos.NewSession(host, user, password)
	if err != nil {
		log.Fatal(err)
	}
	defer jnpr.Close()

	// When you call the PrintFacts() function, it just prints out the platform
	// and software information to the console.
	jnpr.PrintFacts()

	// You can also loop over the struct field that contains this information yourself:
	fmt.Printf("Hostname: %s", jnpr.Hostname)
	for _, data := range jnpr.Platform {
		fmt.Printf("Model: %s, Version: %s", data.Model, data.Version)
	}
	// Output: Model: SRX240H2, Version: 12.1X47-D10.4
}

// Establishing a connection to Junos Space and working with devices.
func ExampleJunosSpace_devices() {
	// Establish a connection to a Junos Space server.
	space := junos.NewServer("space.company.com", "admin", "juniper123")

	// Get the list of devices.
	devices, err := space.Devices()
	if err != nil {
		fmt.Println(err)
	}

	// Iterate over our device list and display some information about them.
	for _, device := range devices.Devices {
		fmt.Printf("Name: %s, IP Address: %s, Platform: %s\n", device.Name, device.IP, device.Platform)
	}

	// Add a device to Junos Space.
	jobID, err = space.AddDevice("sdubs-fw", "admin", "juniper123")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(jobID)
	// Output: 1345283

	// Remove a device from Junos Space.
	err = space.RemoveDevice("sdubs-fw")
	if err != nil {
		fmt.Println(err)
	}
}

// Software upgrades using Junos Space.
func ExampleJunosSpace_softwareUpgrade() {
	// Establish a connection to a Junos Space server.
	space := junos.NewServer("space.company.com", "admin", "juniper123")

	// Staging software on a device. The last parameter is whether or not to remove any
	// existing images from the device; boolean.
	//
	// This will not upgrade the device, but only place the image there to be used at a later
	// time.
	jobID, err := space.StageSoftware("sdubs-fw", "junos-srxsme-12.1X46-D30.2-domestic.tgz", false)
	if err != nil {
		fmt.Println(err)
	}

	// If you want to issue a software upgrade to the device, here's how:

	// Configure our options, such as whether or not to reboot the device, etc.
	options := &junos.SoftwareUpgrade{
		UseDownloaded: true,
		Validate:      false,
		Reboot:        false,
		RebootAfter:   0,
		Cleanup:       false,
		RemoveAfter:   false,
	}

	jobID, err := space.DeploySoftware("sdubs-fw", "junos-srxsme-12.1X46-D30.2-domestic.tgz", options)
	if err != nil {
		fmt.Println(err)
	}

	// Remove a staged image from the device.
	jobID, err := space.RemoveStagedSoftware("sdubs-fw", "junos-srxsme-12.1X46-D30.2-domestic.tgz")
	if err != nil {
		fmt.Println(err)
	}
}

// Viewing information about Security Director devices (SRX, J-series, etc.).
func ExampleJunosSpace_securityDirectorDevices() {
	// Establish a connection to a Junos Space server.
	space := junos.NewServer("space.company.com", "admin", "juniper123")

	// List all security devices:
	devices, err := space.SecurityDevices()
	if err != nil {
		fmt.Println(err)
	}

	for _, device := range devices.Devices {
		fmt.Printf("%+v\n", device)
	}
}

// Working with address and service objects.
func ExampleJunosSpace_addressObjects() {
	// Establish a connection to a Junos Space server.
	space := junos.NewServer("space.company.com", "admin", "juniper123")

	// To view the address and service objects, you use the Addresses() and Services() functions. Both of them
	// take a "filter" parameter, which lets you search for objects matching your filter.

	//If you leave the parameter blank (e.g. ""), or specify "all", then every object is returned.

	// Address objects
	addresses, err := space.Addresses("all")
	if err != nil {
		fmt.Println(err)
	}

	for _, address := range addresses.Addresses {
		fmt.Printf("%+v\n", address)
	}

	// Service objects
	services, err := space.Services("all")
	if err != nil {
		fmt.Println(err)
	}

	for _, service := range services.Services {
		fmt.Printf("%+v\n", service)
	}

	// Add an address group. "true" as the first parameter means that we assume the
	// group is going to be an address group.
	space.AddGroup(true, "Blacklist-IPs", "Blacklisted IP addresses")

	// Add a service group. We do this by specifying "false" as the first parameter.
	space.AddGroup(false, "Web-Protocols", "All web-based protocols and ports")

	// Add an address object
	space.AddAddress("my-laptop", "2.2.2.2", "My personal laptop")

	// Add a network
	space.AddAddress("corporate-users", "192.168.1.0/24", "People on campus")

	// Add a service object with an 1800 second inactivity timeout (using "0" disables this feature)
	space.AddService("udp", "udp-5000", 5000, 5000, "UDP port 5000", 1800)

	// Add a service object with a port range
	space.AddService("tcp", "high-port-range", 40000, 65000, "TCP high ports", 0)

	// If you want to modify an existing object group, you do this with the ModifyObject() function. The
	// first parameter is whether the object is an address group (true) or a service group (false).

	// Add a service to a group
	space.ModifyObject(false, "add", "service-group", "service-name")

	// Remove an address object from a group
	space.ModifyObject(true, "remove", "Whitelisted-Addresses", "bad-ip")

	// Rename an object
	space.ModifyObject(false, "rename", "Web-Services", "Web-Ports")

	// Delete an object
	space.ModifyObject(true, "delete", "my-laptop")
}

// Working with polymorphic (variable) objects.
func ExampleJunosSpace_variables() {
	// Establish a connection to a Junos Space server.
	space := junos.NewServer("space.company.com", "admin", "juniper123")

	// Add a variable
	// The parameters are as follows: variable-name, description, default-value
	space.AddVariable("test-variable", "Our test variable", "default-object")

	// Create our session state for modifying variables
	v, err := space.ModifyVariable()
	if err != nil {
		log.Fatal(err)
	}

	// Adding objects to the variable
	v.Add("test-variable", "srx-1", "user-pc")
	v.Add("test-variable", "corp-firewall", "db-server")

	// Delete a variable
	space.DeleteVariable("test-variable")
}

// Working with policies.
func ExampleJunosSpace_policies() {
	// Establish a connection to a Junos Space server.
	space := junos.NewServer("space.company.com", "admin", "juniper123")

	// List all security policies Junos Space manages:
	policies, err := space.Policies()
	if err != nil {
		fmt.Println(err)
	}

	for _, policy := range policies.Policies {
		fmt.Printf("%s\n", policy.Name)
	}

	// For example, say we have been adding and removing objects in a group, and that group
	// is referenced in a firewall policy. Here's how to update the policy:

	// Update the policy. If "false" is specified, then the policy is only published, and the
	// device is not updated.
	job, err := space.PublishPolicy("Internet-Firewall-Policy", true)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("Job ID: %d\n", job)

	// Let's update a device knowing that we have some previously published services.
	job, err := space.UpdateDevice("firewall-1.company.com")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("Job ID: %d\n", job)
}
<|endoftext|>