<commit_before>// Package peers provides helpers for defining and synchronizing remote
// peers on a network. The package essentially surrounds JSON files with
// remote host definitions in several standard locations. It also provides
// helper functionality for synchronizing the JSON data with a remote server
// as well as adding hosts to the file if they are seen on the network or
// removing hosts if they become offline.
//
// To start using the library, simply create one of the following files:
//
// - /etc/fluidfs/peers.json
// - $HOME/.fluidfs/peers.json
// - $PWD/peers.json
//
// Or define a path using the $PEERS_PATH environment variable. When calling
// peers.Load(), it will look in each of these locations to find and parse
// the peers.json file, returning a Peers object. Alternatively, a new Peers
// object can be created and a path specified to its Load() method to load a
// specific file not above. The peers.json file can also be saved from the
// Peers object using the Dump() method.
//
// The Peers object can also be synchronized from a remote service using the
// Sync() method. Synchronization fetches peers.json from a URL that can be
// specified by the environment, and can also submit an API key along with
// the request.
//
// Other important helpers include the ability to identify the localhost or
// peer from the hostname of the system, or to identify all local peer
// processes. In short, the Peers object is a useful way to manage the
// configuration of a connected network of communicating devices.
package peers

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// Load is the primary entry point for the peers package. It uses a list of
// paths, ordered by priority to find the peers.json file and instantiate the
// peers object from it. If it does not find a peers.json file it simply
// returns an empty collection rather than an error.
//
// The lookup paths for the peers.json file are as follows:
//
// - $PEERS_PATH
// - $PWD/peers.json
// - $HOME/.fluidfs/peers.json
// - /etc/fluidfs/peers.json
//
// At the moment, the first path that is available short circuits the load
// process and all remaining paths are ignored.
func Load() *Peers {
	peers := new(Peers)

	for _, path := range peersPaths() {
		// If there is no error loading the peers, then stop trying to
		// load peers paths because the loading was successful!
		if err := peers.Load(path); err == nil {
			break
		}
	}

	return peers
}

// LoadFrom is a secondary entry point for the peers package. It requires a
// specific path and if there are any errors loading, the error is returned.
func LoadFrom(path string) (*Peers, error) {
	peers := new(Peers)
	err := peers.Load(path)
	return peers, err
}

// Sync is a helper function that performs a SyncFrom() but looks up the
// url and api key from the environment, expecting the following:
//
// - $PEERS_SYNC_URL: url endpoint for sync GET request
// - $PEERS_SYNC_APIKEY: key to add to headers as X-Api-Key
//
// See the SyncFrom function for more details.
func Sync() (*Peers, error) {
	url := os.Getenv("PEERS_SYNC_URL")
	if url == "" {
		return nil, errors.New("could not find $PEERS_SYNC_URL")
	}

	key := os.Getenv("PEERS_SYNC_APIKEY")
	if key == "" {
		return nil, errors.New("could not find $PEERS_SYNC_APIKEY")
	}

	return SyncFrom(url, key)
}

// SyncFrom is a remote entry point for the peers package. It uses an HTTP
// request to synchronize the peers from a remote host and instantiate the
// peers collection. It expects a url and an api key to perform the GET
// request, adding the api key to the headers as "X-Api-Key".
func SyncFrom(url, apikey string) (*Peers, error) {
	// Conduct the request with a 5 second timeout
	client := &http.Client{Timeout: time.Second * 5}
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("X-Api-Key", apikey)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	// Ensure connection is closed on complete
	defer resp.Body.Close()

	// Check the status from the client
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf(
			"could not synchronize peers: %s", resp.Status,
		)
	}

	// Parse the body of the response
	peers := new(Peers)
	if err := json.NewDecoder(resp.Body).Decode(&peers); err != nil {
		return nil, err
	}
	return peers, nil
}

//===========================================================================
// Peers Collection
//===========================================================================

// Peers is a collection of network hosts or processes that can be
// communicated with, along with associated metadata. The Peers object is the
// primary interaction with files on disk and exposes methods that select
// relevent hosts and addresses.
type Peers struct {
	Info  map[string]interface{} `json:"info"`     // metadata associated with the collection
	Peers []*Peer                `json:"replicas"` // the network peers (also called replicas)
	path  string                 // the path that was successfully loaded
}

// Load the peers collection from a JSON file on disk. If the peers are
// successfully loaded, the path it was loaded from is stored and no error
// is returned.
func (p *Peers) Load(path string) error {
	// Read the data from disk
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}

	// Unmarshal the JSON data
	if err := json.Unmarshal(data, &p); err != nil {
		return err
	}

	// Save the path and return nil
	p.path = path
	return nil
}

// Dump the peers collection as a JSON file to disk. If an empty string is
// passed in as an argument, then it will dump to the location on disk it
// was loaded from.
func (p *Peers) Dump(path string) error {

	// Find the correct path to dump to
	if path == "" {
		if p.path == "" {
			return errors.New("no path specified to dump peers.json to")
		}
		path = p.path
	}

	// Make sure the directory exists.
	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
		return err
	}

	// Marshal the JSON data
	data, err := json.MarshalIndent(p, "", "  ")
	if err != nil {
		return err
	}

	// Write the data to the file
	return ioutil.WriteFile(path, data, 0644)
}

// Local returns the peers that are local to the specified host by comparing
// the Peer's Host parameter with the hostname. If the hostname is an empty
// string, then the hostname of the system is used. No errors are returned
// from this function, instead returning an empty list of not found. Because
// the Peer's hostname can be a FQDN, the Host is split on "." and the first
// element is used. If no local replicas are found, it returns an empty list.
func (p *Peers) Local(hostname string) []*Peer {
	// Resolve the hostname
	if hostname == "" {
		hostname, _ = os.Hostname()
	}

	peers := make([]*Peer, 0)
	for _, peer := range p.Peers {
		name := strings.Split(peer.Hostname, ".")[0]
		if name == hostname {
			peers = append(peers, peer)
		}
	}

	return peers

}

// Localhost returns the peer that is defined by the current localhost. Note
// that multiple peers can reside on a single machine, but this method will
// only return one Peer. Filtering multiple local replicas can be done with
// a precedence ID. If no ID is specified (e.g. 0) then the first local peer
// is returned. If no matching peer is found then an error is returned.
func (p *Peers) Localhost(hostname string, pid uint16) (*Peer, error) {
	for _, peer := range p.Local(hostname) {
		if pid == 0 || peer.PID == pid {
			return peer, nil
		}
	}

	return nil, errors.New("could not find a matching localhost")
}

// Get a specific peer by name (which should be unique). If the named peer is
// not found, then an error is returned.
func (p *Peers) Get(hostname string) (*Peer, error) {
	for _, peer := range p.Peers {
		if peer.Name == hostname {
			return peer, nil
		}
	}

	return nil, fmt.Errorf("could not find a peer named '%s'", hostname)
}

//===========================================================================
// Peer Struct
//===========================================================================

// Peer represents a single instance of another replica process or host on
// the network that can be communicated with.
type Peer struct {
	PID         uint16 `json:"pid"`                   // the precedence id of the peer
	Name        string `json:"name"`                  // unique name of the peer
	Description string `json:"description,omitempty"` // a text description of the peer
	Hostname    string `json:"hostname,omitempty"`    // the hostname of the peer
	IPAddr      string `json:"ip_address"`            // the ip address of the peer
	Domain      string `json:"domain,omitempty"`      // the domain name of hte host
	Port        uint16 `json:"port"`                  // the port the replica is listening on

	// Extra information that may be associated with the host
	AWSInstance map[string]string `json:"aws_instance,omitempty"`
}

// IsLocal returns True if the Peer has the same hostname as the localhost.
// Because the host can be specified as a FQDN, this method splits the name
// on "." and inspects the first element of the name.
func (p *Peer) IsLocal() bool {
	hostname, err := os.Hostname()
	if err != nil {
		return false
	}

	name := strings.Split(p.Hostname, ".")[0]
	return name == hostname
}

// ZMQEndpoint returns an endpoing to bind or connect on specifically for the
// ZMQ protocol, that is a TCP protocol socket on the specified port.
//
// If server is true, then a bind address of tcp://*:port is returned so that
// messages received on any IP address at that port are handled. If server is
// false then one of two things happen. If the peer IsLocal() then
// tcp://localhost:port is returned. Otherwise tcp://IPAddr:port is returned.
func (p *Peer) ZMQEndpoint(server bool) string {
	if server {
		return fmt.Sprintf("tcp://*:%d", p.Port)
	}

	if p.IsLocal() {
		return fmt.Sprintf("tcp://localhost:%d", p.Port)
	}

	return fmt.Sprintf("tcp://%s:%d", p.IPAddr, p.Port)
}
<commit_msg>grpc endpoint<commit_after>// Package peers provides helpers for defining and synchronizing remote
// peers on a network. The package essentially surrounds JSON files with
// remote host definitions in several standard locations. It also provides
// helper functionality for synchronizing the JSON data with a remote server
// as well as adding hosts to the file if they are seen on the network or
// removing hosts if they become offline.
//
// To start using the library, simply create one of the following files:
//
// - /etc/fluidfs/peers.json
// - $HOME/.fluidfs/peers.json
// - $PWD/peers.json
//
// Or define a path using the $PEERS_PATH environment variable. When calling
// peers.Load(), it will look in each of these locations to find and parse
// the peers.json file, returning a Peers object. Alternatively, a new Peers
// object can be created and a path specified to its Load() method to load a
// specific file not above. The peers.json file can also be saved from the
// Peers object using the Dump() method.
//
// The Peers object can also be synchronized from a remote service using the
// Sync() method. Synchronization fetches peers.json from a URL that can be
// specified by the environment, and can also submit an API key along with
// the request.
//
// Other important helpers include the ability to identify the localhost or
// peer from the hostname of the system, or to identify all local peer
// processes. In short, the Peers object is a useful way to manage the
// configuration of a connected network of communicating devices.
package peers

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

// Load is the primary entry point for the peers package. It uses a list of
// paths, ordered by priority to find the peers.json file and instantiate the
// peers object from it. If it does not find a peers.json file it simply
// returns an empty collection rather than an error.
//
// The lookup paths for the peers.json file are as follows:
//
// - $PEERS_PATH
// - $PWD/peers.json
// - $HOME/.fluidfs/peers.json
// - /etc/fluidfs/peers.json
//
// At the moment, the first path that is available short circuits the load
// process and all remaining paths are ignored.
func Load() *Peers {
	peers := new(Peers)

	for _, path := range peersPaths() {
		// If there is no error loading the peers, then stop trying to
		// load peers paths because the loading was successful!
		if err := peers.Load(path); err == nil {
			break
		}
	}

	return peers
}

// LoadFrom is a secondary entry point for the peers package. It requires a
// specific path and if there are any errors loading, the error is returned.
func LoadFrom(path string) (*Peers, error) {
	peers := new(Peers)
	err := peers.Load(path)
	return peers, err
}

// Sync is a helper function that performs a SyncFrom() but looks up the
// url and api key from the environment, expecting the following:
//
// - $PEERS_SYNC_URL: url endpoint for sync GET request
// - $PEERS_SYNC_APIKEY: key to add to headers as X-Api-Key
//
// See the SyncFrom function for more details.
func Sync() (*Peers, error) {
	url := os.Getenv("PEERS_SYNC_URL")
	if url == "" {
		return nil, errors.New("could not find $PEERS_SYNC_URL")
	}

	key := os.Getenv("PEERS_SYNC_APIKEY")
	if key == "" {
		return nil, errors.New("could not find $PEERS_SYNC_APIKEY")
	}

	return SyncFrom(url, key)
}

// SyncFrom is a remote entry point for the peers package. It uses an HTTP
// request to synchronize the peers from a remote host and instantiate the
// peers collection. It expects a url and an api key to perform the GET
// request, adding the api key to the headers as "X-Api-Key".
func SyncFrom(url, apikey string) (*Peers, error) {
	// Conduct the request with a 5 second timeout
	client := &http.Client{Timeout: time.Second * 5}
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("X-Api-Key", apikey)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	// Ensure connection is closed on complete
	defer resp.Body.Close()

	// Check the status from the client
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf(
			"could not synchronize peers: %s", resp.Status,
		)
	}

	// Parse the body of the response
	peers := new(Peers)
	if err := json.NewDecoder(resp.Body).Decode(&peers); err != nil {
		return nil, err
	}
	return peers, nil
}

//===========================================================================
// Peers Collection
//===========================================================================

// Peers is a collection of network hosts or processes that can be
// communicated with, along with associated metadata. The Peers object is the
// primary interaction with files on disk and exposes methods that select
// relevent hosts and addresses.
type Peers struct {
	Info  map[string]interface{} `json:"info"`     // metadata associated with the collection
	Peers []*Peer                `json:"replicas"` // the network peers (also called replicas)
	path  string                 // the path that was successfully loaded
}

// Load the peers collection from a JSON file on disk. If the peers are
// successfully loaded, the path it was loaded from is stored and no error
// is returned.
func (p *Peers) Load(path string) error {
	// Read the data from disk
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}

	// Unmarshal the JSON data
	if err := json.Unmarshal(data, &p); err != nil {
		return err
	}

	// Save the path and return nil
	p.path = path
	return nil
}

// Dump the peers collection as a JSON file to disk. If an empty string is
// passed in as an argument, then it will dump to the location on disk it
// was loaded from.
func (p *Peers) Dump(path string) error {

	// Find the correct path to dump to
	if path == "" {
		if p.path == "" {
			return errors.New("no path specified to dump peers.json to")
		}
		path = p.path
	}

	// Make sure the directory exists.
	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
		return err
	}

	// Marshal the JSON data
	data, err := json.MarshalIndent(p, "", "  ")
	if err != nil {
		return err
	}

	// Write the data to the file
	return ioutil.WriteFile(path, data, 0644)
}

// Local returns the peers that are local to the specified host by comparing
// the Peer's Host parameter with the hostname. If the hostname is an empty
// string, then the hostname of the system is used. No errors are returned
// from this function, instead returning an empty list of not found. Because
// the Peer's hostname can be a FQDN, the Host is split on "." and the first
// element is used. If no local replicas are found, it returns an empty list.
func (p *Peers) Local(hostname string) []*Peer {
	// Resolve the hostname
	if hostname == "" {
		hostname, _ = os.Hostname()
	}

	peers := make([]*Peer, 0)
	for _, peer := range p.Peers {
		name := strings.Split(peer.Hostname, ".")[0]
		if name == hostname {
			peers = append(peers, peer)
		}
	}

	return peers

}

// Localhost returns the peer that is defined by the current localhost. Note
// that multiple peers can reside on a single machine, but this method will
// only return one Peer. Filtering multiple local replicas can be done with
// a precedence ID. If no ID is specified (e.g. 0) then the first local peer
// is returned. If no matching peer is found then an error is returned.
func (p *Peers) Localhost(hostname string, pid uint16) (*Peer, error) {
	for _, peer := range p.Local(hostname) {
		if pid == 0 || peer.PID == pid {
			return peer, nil
		}
	}

	return nil, errors.New("could not find a matching localhost")
}

// Get a specific peer by name (which should be unique). If the named peer is
// not found, then an error is returned.
func (p *Peers) Get(hostname string) (*Peer, error) {
	for _, peer := range p.Peers {
		if peer.Name == hostname {
			return peer, nil
		}
	}

	return nil, fmt.Errorf("could not find a peer named '%s'", hostname)
}

//===========================================================================
// Peer Struct
//===========================================================================

// Peer represents a single instance of another replica process or host on
// the network that can be communicated with.
type Peer struct {
	PID         uint16 `json:"pid"`                   // the precedence id of the peer
	Name        string `json:"name"`                  // unique name of the peer
	Description string `json:"description,omitempty"` // a text description of the peer
	Hostname    string `json:"hostname,omitempty"`    // the hostname of the peer
	IPAddr      string `json:"ip_address"`            // the ip address of the peer
	Domain      string `json:"domain,omitempty"`      // the domain name of hte host
	Port        uint16 `json:"port"`                  // the port the replica is listening on

	// Extra information that may be associated with the host
	AWSInstance map[string]string `json:"aws_instance,omitempty"`
}

// IsLocal returns True if the Peer has the same hostname as the localhost.
// Because the host can be specified as a FQDN, this method splits the name
// on "." and inspects the first element of the name.
func (p *Peer) IsLocal() bool {
	hostname, err := os.Hostname()
	if err != nil {
		return false
	}

	name := strings.Split(p.Hostname, ".")[0]
	return name == hostname
}

// Endpoint returns an string with the ip address and the port (or the domain)
// to connect to the peer using TCP.
func (p *Peer) Endpoint(dns bool) string {
	if dns && p.Domain != "" {
		return fmt.Sprintf("%s:%d", p.Domain, p.Port)
	}

	return fmt.Sprintf("%s:%d", p.IPAddr, p.Port)
}

// ZMQEndpoint returns an endpoint to bind or connect on specifically for the
// ZMQ protocol, that is a TCP protocol socket on the specified port.
//
// If server is true, then a bind address of tcp://*:port is returned so that
// messages received on any IP address at that port are handled. If server is
// false then one of two things happen. If the peer IsLocal() then
// tcp://localhost:port is returned. Otherwise tcp://IPAddr:port is returned.
func (p *Peer) ZMQEndpoint(server bool) string {
	if server {
		return fmt.Sprintf("tcp://*:%d", p.Port)
	}

	if p.IsLocal() {
		return fmt.Sprintf("tcp://localhost:%d", p.Port)
	}

	return fmt.Sprintf("tcp://%s:%d", p.IPAddr, p.Port)
}
<|endoftext|>


<commit_before>package service_test

import (
	"github.com/cloudfoundry/cli/cf/api"
	"github.com/cloudfoundry/cli/cf/configuration"
	"github.com/cloudfoundry/cli/cf/models"
	testapi "github.com/cloudfoundry/cli/testhelpers/api"
	testcmd "github.com/cloudfoundry/cli/testhelpers/commands"
	testconfig "github.com/cloudfoundry/cli/testhelpers/configuration"
	testreq "github.com/cloudfoundry/cli/testhelpers/requirements"
	testterm "github.com/cloudfoundry/cli/testhelpers/terminal"

	. "github.com/cloudfoundry/cli/cf/commands/service"
	. "github.com/cloudfoundry/cli/testhelpers/matchers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

func callUpdateUserProvidedService(args []string, requirementsFactory *testreq.FakeReqFactory, userProvidedServiceInstanceRepo api.UserProvidedServiceInstanceRepository) (fakeUI *testterm.FakeUI) {
	fakeUI = &testterm.FakeUI{}
	config := testconfig.NewRepositoryWithDefaults()

	cmd := NewUpdateUserProvidedService(fakeUI, config, userProvidedServiceInstanceRepo)
	testcmd.RunCommand(cmd, args, requirementsFactory)
	return
}

var _ = Describe("update-user-provided-service test", func() {
	var (
		ui                  *testterm.FakeUI
		configRepo          configuration.ReadWriter
		serviceRepo         *testapi.FakeUserProvidedServiceInstanceRepo
		requirementsFactory *testreq.FakeReqFactory
	)

	BeforeEach(func() {
		ui = &testterm.FakeUI{}
		serviceRepo = &testapi.FakeUserProvidedServiceInstanceRepo{}
		configRepo = testconfig.NewRepositoryWithDefaults()
		requirementsFactory = &testreq.FakeReqFactory{}
	})

	runCommand := func(args ...string) {
		cmd := NewUpdateUserProvidedService(ui, configRepo, serviceRepo)
		testcmd.RunCommand(cmd, args, requirementsFactory)
	}

	Describe("requirements", func() {
		It("fails with usage when not provided the name of the service to update", func() {
			requirementsFactory.LoginSuccess = true
			runCommand()
			Expect(ui.FailedWithUsage).To(BeTrue())
		})

		It("fails when not logged in", func() {
			runCommand("whoops")
			Expect(testcmd.CommandDidPassRequirements).To(BeFalse())
		})
	})

	Context("when logged in", func() {
		BeforeEach(func() {
			requirementsFactory.LoginSuccess = true

			serviceInstance := models.ServiceInstance{}
			serviceInstance.Name = "service-name"
			requirementsFactory.ServiceInstance = serviceInstance
		})

		Context("when no flags are provided", func() {
			It("tells the user that no changes occurred", func() {
				runCommand("service-name")

				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Updating user provided service", "service-name", "my-org", "my-space", "my-user"},
					[]string{"OK"},
					[]string{"No changes"},
				))
			})
		})

		Context("when the user provides valid JSON with the -p flag", func() {
			It("updates the user provided service specified", func() {
				runCommand("-p", `{"foo":"bar"}`, "-l", "syslog://example.com", "service-name")

				Expect(requirementsFactory.ServiceInstanceName).To(Equal("service-name"))
				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Updating user provided service", "service-name", "my-org", "my-space", "my-user"},
					[]string{"OK"},
					[]string{"TIP"},
				))
				Expect(serviceRepo.UpdateServiceInstance.Name).To(Equal("service-name"))
				Expect(serviceRepo.UpdateServiceInstance.Params).To(Equal(map[string]string{"foo": "bar"}))
				Expect(serviceRepo.UpdateServiceInstance.SysLogDrainUrl).To(Equal("syslog://example.com"))
			})
		})

		Context("when the user provides invalid JSON with the -p flag", func() {
			It("tells the user the JSON is invalid", func() {
				runCommand("-p", `{"foo":"ba WHOOPS OH MY HOW DID THIS GET HERE???`, "service-name")

				Expect(serviceRepo.UpdateServiceInstance).To(Equal(models.ServiceInstanceFields{}))
				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"FAILED"},
					[]string{"JSON is invalid"},
				))
			})
		})

		Context("when the service with the given name is not user provided", func() {
			BeforeEach(func() {
				plan := models.ServicePlanFields{Guid: "my-plan-guid"}
				serviceInstance := models.ServiceInstance{}
				serviceInstance.Name = "found-service-name"
				serviceInstance.ServicePlan = plan

				requirementsFactory.ServiceInstance = serviceInstance
			})

			It("fails and tells the user what went wrong", func() {
				runCommand("-p", `{"foo":"bar"}`, "service-name")

				Expect(serviceRepo.UpdateServiceInstance).To(Equal(models.ServiceInstanceFields{}))
				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"FAILED"},
					[]string{"Service Instance is not user provided"},
				))
			})
		})
	})
})
<commit_msg>Kill an unused function<commit_after>package service_test

import (
	"github.com/cloudfoundry/cli/cf/configuration"
	"github.com/cloudfoundry/cli/cf/models"
	testapi "github.com/cloudfoundry/cli/testhelpers/api"
	testcmd "github.com/cloudfoundry/cli/testhelpers/commands"
	testconfig "github.com/cloudfoundry/cli/testhelpers/configuration"
	testreq "github.com/cloudfoundry/cli/testhelpers/requirements"
	testterm "github.com/cloudfoundry/cli/testhelpers/terminal"

	. "github.com/cloudfoundry/cli/cf/commands/service"
	. "github.com/cloudfoundry/cli/testhelpers/matchers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("update-user-provided-service test", func() {
	var (
		ui                  *testterm.FakeUI
		configRepo          configuration.ReadWriter
		serviceRepo         *testapi.FakeUserProvidedServiceInstanceRepo
		requirementsFactory *testreq.FakeReqFactory
	)

	BeforeEach(func() {
		ui = &testterm.FakeUI{}
		serviceRepo = &testapi.FakeUserProvidedServiceInstanceRepo{}
		configRepo = testconfig.NewRepositoryWithDefaults()
		requirementsFactory = &testreq.FakeReqFactory{}
	})

	runCommand := func(args ...string) {
		cmd := NewUpdateUserProvidedService(ui, configRepo, serviceRepo)
		testcmd.RunCommand(cmd, args, requirementsFactory)
	}

	Describe("requirements", func() {
		It("fails with usage when not provided the name of the service to update", func() {
			requirementsFactory.LoginSuccess = true
			runCommand()
			Expect(ui.FailedWithUsage).To(BeTrue())
		})

		It("fails when not logged in", func() {
			runCommand("whoops")
			Expect(testcmd.CommandDidPassRequirements).To(BeFalse())
		})
	})

	Context("when logged in", func() {
		BeforeEach(func() {
			requirementsFactory.LoginSuccess = true

			serviceInstance := models.ServiceInstance{}
			serviceInstance.Name = "service-name"
			requirementsFactory.ServiceInstance = serviceInstance
		})

		Context("when no flags are provided", func() {
			It("tells the user that no changes occurred", func() {
				runCommand("service-name")

				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Updating user provided service", "service-name", "my-org", "my-space", "my-user"},
					[]string{"OK"},
					[]string{"No changes"},
				))
			})
		})

		Context("when the user provides valid JSON with the -p flag", func() {
			It("updates the user provided service specified", func() {
				runCommand("-p", `{"foo":"bar"}`, "-l", "syslog://example.com", "service-name")

				Expect(requirementsFactory.ServiceInstanceName).To(Equal("service-name"))
				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"Updating user provided service", "service-name", "my-org", "my-space", "my-user"},
					[]string{"OK"},
					[]string{"TIP"},
				))
				Expect(serviceRepo.UpdateServiceInstance.Name).To(Equal("service-name"))
				Expect(serviceRepo.UpdateServiceInstance.Params).To(Equal(map[string]string{"foo": "bar"}))
				Expect(serviceRepo.UpdateServiceInstance.SysLogDrainUrl).To(Equal("syslog://example.com"))
			})
		})

		Context("when the user provides invalid JSON with the -p flag", func() {
			It("tells the user the JSON is invalid", func() {
				runCommand("-p", `{"foo":"ba WHOOPS OH MY HOW DID THIS GET HERE???`, "service-name")

				Expect(serviceRepo.UpdateServiceInstance).To(Equal(models.ServiceInstanceFields{}))
				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"FAILED"},
					[]string{"JSON is invalid"},
				))
			})
		})

		Context("when the service with the given name is not user provided", func() {
			BeforeEach(func() {
				plan := models.ServicePlanFields{Guid: "my-plan-guid"}
				serviceInstance := models.ServiceInstance{}
				serviceInstance.Name = "found-service-name"
				serviceInstance.ServicePlan = plan

				requirementsFactory.ServiceInstance = serviceInstance
			})

			It("fails and tells the user what went wrong", func() {
				runCommand("-p", `{"foo":"bar"}`, "service-name")

				Expect(serviceRepo.UpdateServiceInstance).To(Equal(models.ServiceInstanceFields{}))
				Expect(ui.Outputs).To(ContainSubstrings(
					[]string{"FAILED"},
					[]string{"Service Instance is not user provided"},
				))
			})
		})
	})
})
<|endoftext|>


<commit_before>package gopoet

import . "gopkg.in/check.v1"

type ImportsSuite struct{}

var _ = Suite(&ImportsSuite{})

func (f *ImportsSuite) TestQualifiedImport(c *C) {
	expected := "bytes."
	imp := &ImportSpec{
		Package:   "bytes",
		Qualified: true,
	}
	actual := imp.getQualifier()
	c.Assert(actual, Equals, expected)
}

func (f *ImportsSuite) TestUnqualifiedImport(c *C) {
	expected := ""
	imp := &ImportSpec{
		Package:   "bytes",
		Qualified: false,
	}
	actual := imp.getQualifier()
	c.Assert(actual, Equals, expected)
}
<commit_msg>increase imports test coverage<commit_after>package gopoet

import . "gopkg.in/check.v1"

type ImportsSuite struct{}

var _ = Suite(&ImportsSuite{})

func (f *ImportsSuite) TestQualifiedImport(c *C) {
	expected := "bytes."
	imp := &ImportSpec{
		Package:   "bytes",
		Qualified: true,
	}
	actual := imp.getQualifier()
	c.Assert(actual, Equals, expected)
}

func (f *ImportsSuite) TestUnqualifiedImport(c *C) {
	expected := ""
	imp := &ImportSpec{
		Package:   "bytes",
		Qualified: false,
	}
	actual := imp.getQualifier()
	c.Assert(actual, Equals, expected)
}

func (f *ImportsSuite) TestAliasedImport(c *C) {
	expected := "blah."
	imp := &ImportSpec{
		Package:   "bytes",
		Qualified: true,
		Alias:     "blah",
	}
	actual := imp.getQualifier()
	c.Assert(imp.GetAlias(), Equals, "blah")
	c.Assert(imp.GetPackage(), Equals, "bytes")
	c.Assert(actual, Equals, expected)
}

func (f *ImportsSuite) TestNils(c *C) {
	nilInst := ((*ImportSpec)(nil))
	c.Assert(nilInst.GetAlias(), Equals, "")
	c.Assert(nilInst.GetPackage(), Equals, "")
}
<|endoftext|>


<commit_before>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package setup

import (
	"context"
	"errors"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"

	"github.com/admpub/log"
	"github.com/admpub/nging/application/handler"
	"github.com/admpub/nging/application/library/config"
	"github.com/admpub/nging/application/model"
)

type ProgressInfo struct {
	Finished  int64
	TotalSize int64
	Summary   string
	Timestamp int64
}

var (
	installProgress   *ProgressInfo
	installedProgress = &ProgressInfo{
		Finished:  1,
		TotalSize: 1,
	}
	uninstallProgress = &ProgressInfo{
		Finished:  0,
		TotalSize: 1,
	}

	OnInstalled func(ctx context.Context) error
)

func init() {
	handler.Register(func(e echo.RouteRegister) {
		e.Route("GET,POST", `/setup`, Setup)
		e.Route("GET", `/progress`, Progress)
		e.Route("GET,POST", `/license`, License)
	})
}

func Progress(ctx echo.Context) error {
	data := ctx.Data()
	if config.IsInstalled() {
		data.SetInfo(ctx.T(`已经安装过了`), 0)
		data.SetData(installedProgress)
	} else {
		if installProgress == nil {
			data.SetInfo(ctx.T(`尚未开始`), 1)
			data.SetData(uninstallProgress)
		} else {
			data.SetInfo(ctx.T(`安装中`), 1)
			data.SetData(installProgress)
		}
	}
	return ctx.JSON(data)
}

func install(ctx echo.Context, sqlFile string, lockFile string) (err error) {
	installProgress = &ProgressInfo{
		Timestamp: time.Now().Local().Unix(),
	}
	defer func() {
		if err != nil {
			installProgress = nil
		}
	}()
	m := model.NewUser(ctx)
	var (
		sqlStr    string
		installer func(string) error
		ok        bool
	)
	adminUser := ctx.Form(`adminUser`)
	adminPass := ctx.Form(`adminPass`)
	adminEmail := ctx.Form(`adminEmail`)
	if len(adminUser) == 0 {
		err = ctx.E(`管理员用户名不能为空`)
		return
	}
	if !com.IsUsername(adminUser) {
		err = errors.New(ctx.T(`管理员名不能包含特殊字符(只能由字母、数字、下划线和汉字组成)`))
		return
	}
	if len(adminPass) < 8 {
		err = ctx.E(`管理员密码不能少于8个字符`)
		return
	}
	if len(adminEmail) == 0 {
		err = ctx.E(`管理员邮箱不能为空`)
		return
	}
	if !ctx.Validate(`adminEmail`, adminEmail, `email`).Ok() {
		err = ctx.E(`管理员邮箱格式不正确`)
		return
	}
	err = ctx.MustBind(&config.DefaultConfig.DB)
	config.DefaultConfig.DB.Database = strings.Replace(config.DefaultConfig.DB.Database, "'", "", -1)
	config.DefaultConfig.DB.Database = strings.Replace(config.DefaultConfig.DB.Database, "`", "", -1)
	if config.DefaultConfig.DB.Type == `sqlite` {
		config.DefaultConfig.DB.User = ``
		config.DefaultConfig.DB.Password = ``
		config.DefaultConfig.DB.Host = ``
		if strings.HasSuffix(config.DefaultConfig.DB.Database, `.db`) == false {
			config.DefaultConfig.DB.Database += `.db`
		}
	}
	if err != nil {
		return
	}
	//连接数据库
	err = config.ConnectDB(config.DefaultConfig)
	if err != nil {
		err = createDatabase(err)
	}
	if err != nil {
		return
	}
	//创建数据库数据
	installer, ok = config.DBInstallers[config.DefaultConfig.DB.Type]
	if !ok {
		err = ctx.E(`不支持安装到%s`, config.DefaultConfig.DB.Type)
		return
	}
	installProgress.TotalSize, err = com.FileSize(sqlFile)
	if err != nil {
		return
	}
	installProgress.TotalSize += int64(len(handler.OfficialSQL))
	installFunction := func(line string) (rErr error) {
		installProgress.Finished += int64(len(line)) + 1
		if strings.HasPrefix(line, `--`) {
			return nil
		}
		if strings.HasPrefix(line, `/*`) && strings.HasSuffix(line, `*/;`) {
			return nil
		}
		line = strings.TrimSpace(line)
		sqlStr += line
		if strings.HasSuffix(line, `;`) && len(sqlStr) > 0 {
			//installProgress.Summary = sqlStr
			defer func() {
				sqlStr = ``
			}()
			return installer(sqlStr)
		}
		return nil
	}
	err = com.SeekFileLines(sqlFile, installFunction)
	if err != nil {
		return
	}
	for _, line := range strings.Split(handler.OfficialSQL, "\n") {
		err = installFunction(line)
		if err != nil {
			return
		}
	}
	err = m.Register(adminUser, adminPass, adminEmail)
	if err != nil {
		return
	}
	config.DefaultConfig.InitSecretKey()

	//保存数据库账号到配置文件
	err = config.DefaultConfig.SaveToFile()
	if err != nil {
		return
	}

	//生成锁文件
	err = config.SetInstalled(lockFile)
	if err == nil && OnInstalled != nil {
		err = OnInstalled(ctx)
	}
	return
}

func Setup(ctx echo.Context) error {
	var err error
	lockFile := filepath.Join(echo.Wd(), `installed.lock`)
	if info, err := os.Stat(lockFile); err == nil && info.IsDir() == false {
		msg := ctx.T(`已经安装过了。如要重新安装，请先删除%s`, lockFile)
		if ctx.IsAjax() {
			return ctx.JSON(ctx.Data().SetInfo(msg, 0))
		}
		return ctx.String(msg)
	}
	sqlFiles, err := config.GetSQLInstallFiles()
	if err != nil {
		msg := ctx.T(`找不到文件%s，无法安装`, `config/install.sql`)
		if ctx.IsAjax() {
			return ctx.JSON(ctx.Data().SetInfo(msg, 0))
		}
		return ctx.String(msg)
	}
	if ctx.IsPost() && installProgress == nil {
		data := ctx.Data()
		for _, sqlFile := range sqlFiles {
			err = install(ctx, sqlFile, lockFile)
			if err != nil {
				break
			}
		}
		defer func() {
			if err == nil {
				time.Sleep(1 * time.Second)
				config.DefaultCLIConfig.RunStartup()
				if err := Upgrade(); err != nil {
					log.Error(err)
				}
			}
		}()
		if ctx.IsAjax() {
			if err != nil {
				data.SetError(err)
			} else {
				data.SetInfo(ctx.T(`安装成功`)).SetData(installProgress)
			}
			return ctx.JSON(data)
		} else {
			if err != nil {
				goto DIE
			}
		}
		handler.SendOk(ctx, ctx.T(`安装成功`))
		return ctx.Redirect(handler.URLFor(`/`))
	}

DIE:
	ctx.Set(`dbEngines`, config.DBEngines.Slice())
	return ctx.Render(`setup`, handler.Err(ctx, err))
}

func createDatabase(err error) error {
	if fn, ok := config.DBCreaters[config.DefaultConfig.DB.Type]; ok {
		return fn(err, config.DefaultConfig)
	}
	return err
}
<commit_msg>update<commit_after>/*
   Nging is a toolbox for webmasters
   Copyright (C) 2018-present  Wenhui Shen <swh@admpub.com>

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as published
   by the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

   You should have received a copy of the GNU Affero General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package setup

import (
	"context"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/webx-top/com"
	"github.com/webx-top/echo"

	"github.com/admpub/log"
	"github.com/admpub/nging/application/handler"
	"github.com/admpub/nging/application/library/config"
	"github.com/admpub/nging/application/model"
)

type ProgressInfo struct {
	Finished  int64
	TotalSize int64
	Summary   string
	Timestamp int64
}

var (
	installProgress   *ProgressInfo
	installedProgress = &ProgressInfo{
		Finished:  1,
		TotalSize: 1,
	}
	uninstallProgress = &ProgressInfo{
		Finished:  0,
		TotalSize: 1,
	}

	OnInstalled func(ctx context.Context) error
)

func init() {
	handler.Register(func(e echo.RouteRegister) {
		e.Route("GET,POST", `/setup`, Setup)
		e.Route("GET", `/progress`, Progress)
		e.Route("GET,POST", `/license`, License)
	})
}

func Progress(ctx echo.Context) error {
	data := ctx.Data()
	if config.IsInstalled() {
		data.SetInfo(ctx.T(`已经安装过了`), 0)
		data.SetData(installedProgress)
	} else {
		if installProgress == nil {
			data.SetInfo(ctx.T(`尚未开始`), 1)
			data.SetData(uninstallProgress)
		} else {
			data.SetInfo(ctx.T(`安装中`), 1)
			data.SetData(installProgress)
		}
	}
	return ctx.JSON(data)
}

func install(ctx echo.Context, sqlFile string) (err error) {
	installProgress = &ProgressInfo{
		Timestamp: time.Now().Local().Unix(),
	}
	defer func() {
		if err != nil {
			installProgress = nil
		}
	}()
	var (
		sqlStr    string
		installer func(string) error
		ok        bool
	)
	err = ctx.MustBind(&config.DefaultConfig.DB)
	config.DefaultConfig.DB.Database = strings.Replace(config.DefaultConfig.DB.Database, "'", "", -1)
	config.DefaultConfig.DB.Database = strings.Replace(config.DefaultConfig.DB.Database, "`", "", -1)
	if config.DefaultConfig.DB.Type == `sqlite` {
		config.DefaultConfig.DB.User = ``
		config.DefaultConfig.DB.Password = ``
		config.DefaultConfig.DB.Host = ``
		if strings.HasSuffix(config.DefaultConfig.DB.Database, `.db`) == false {
			config.DefaultConfig.DB.Database += `.db`
		}
	}
	if err != nil {
		return
	}
	//连接数据库
	err = config.ConnectDB(config.DefaultConfig)
	if err != nil {
		err = createDatabase(err)
	}
	if err != nil {
		return
	}
	//创建数据库数据
	installer, ok = config.DBInstallers[config.DefaultConfig.DB.Type]
	if !ok {
		err = ctx.E(`不支持安装到%s`, config.DefaultConfig.DB.Type)
		return
	}
	installProgress.TotalSize, err = com.FileSize(sqlFile)
	if err != nil {
		return
	}
	installProgress.TotalSize += int64(len(handler.OfficialSQL))
	installFunction := func(line string) (rErr error) {
		installProgress.Finished += int64(len(line)) + 1
		if strings.HasPrefix(line, `--`) {
			return nil
		}
		if strings.HasPrefix(line, `/*`) && strings.HasSuffix(line, `*/;`) {
			return nil
		}
		line = strings.TrimSpace(line)
		sqlStr += line
		if strings.HasSuffix(line, `;`) && len(sqlStr) > 0 {
			//installProgress.Summary = sqlStr
			defer func() {
				sqlStr = ``
			}()
			return installer(sqlStr)
		}
		return nil
	}
	err = com.SeekFileLines(sqlFile, installFunction)
	if err != nil {
		return
	}
	for _, line := range strings.Split(handler.OfficialSQL, "\n") {
		err = installFunction(line)
		if err != nil {
			return
		}
	}
	return
}

func Setup(ctx echo.Context) error {
	var err error
	lockFile := filepath.Join(echo.Wd(), `installed.lock`)
	if info, err := os.Stat(lockFile); err == nil && info.IsDir() == false {
		msg := ctx.T(`已经安装过了。如要重新安装，请先删除%s`, lockFile)
		if ctx.IsAjax() {
			return ctx.JSON(ctx.Data().SetInfo(msg, 0))
		}
		return ctx.String(msg)
	}
	sqlFiles, err := config.GetSQLInstallFiles()
	if err != nil {
		msg := ctx.T(`找不到文件%s，无法安装`, `config/install.sql`)
		if ctx.IsAjax() {
			return ctx.JSON(ctx.Data().SetInfo(msg, 0))
		}
		return ctx.String(msg)
	}
	if ctx.IsPost() && installProgress == nil {
		adminUser := ctx.Form(`adminUser`)
		adminPass := ctx.Form(`adminPass`)
		adminEmail := ctx.Form(`adminEmail`)
		if len(adminUser) == 0 {
			err = ctx.E(`管理员用户名不能为空`)
			return err
		}
		if !com.IsUsername(adminUser) {
			err = ctx.E(`管理员名不能包含特殊字符(只能由字母、数字、下划线和汉字组成)`)
			return err
		}
		if len(adminPass) < 8 {
			err = ctx.E(`管理员密码不能少于8个字符`)
			return err
		}
		if len(adminEmail) == 0 {
			err = ctx.E(`管理员邮箱不能为空`)
			return err
		}
		if !ctx.Validate(`adminEmail`, adminEmail, `email`).Ok() {
			err = ctx.E(`管理员邮箱格式不正确`)
			return err
		}
		data := ctx.Data()
		for _, sqlFile := range sqlFiles {
			err = install(ctx, sqlFile)
			if err != nil {
				break
			}
		}
		defer func() {
			if err == nil {
				time.Sleep(1 * time.Second)
				config.DefaultCLIConfig.RunStartup()
				if err := Upgrade(); err != nil {
					log.Error(err)
				}
				m := model.NewUser(ctx)
				err = m.Register(adminUser, adminPass, adminEmail)
				if err != nil {
					return
				}
				config.DefaultConfig.InitSecretKey()

				//保存数据库账号到配置文件
				err = config.DefaultConfig.SaveToFile()
				if err != nil {
					return
				}

				//生成锁文件
				err = config.SetInstalled(lockFile)
				if err == nil && OnInstalled != nil {
					err = OnInstalled(ctx)
				}
			}
		}()
		if ctx.IsAjax() {
			if err != nil {
				data.SetError(err)
			} else {
				data.SetInfo(ctx.T(`安装成功`)).SetData(installProgress)
			}
			return ctx.JSON(data)
		}
		if err != nil {
			goto DIE
		}
		handler.SendOk(ctx, ctx.T(`安装成功`))
		return ctx.Redirect(handler.URLFor(`/`))
	}

DIE:
	ctx.Set(`dbEngines`, config.DBEngines.Slice())
	return ctx.Render(`setup`, handler.Err(ctx, err))
}

func createDatabase(err error) error {
	if fn, ok := config.DBCreaters[config.DefaultConfig.DB.Type]; ok {
		return fn(err, config.DefaultConfig)
	}
	return err
}
<|endoftext|>


<commit_before>package app

import (
	"fmt"
	"net/http"
	"os"

	"github.com/fusor/ansible-service-broker/pkg/ansibleapp"
	"github.com/fusor/ansible-service-broker/pkg/broker"
	"github.com/fusor/ansible-service-broker/pkg/dao"
	"github.com/fusor/ansible-service-broker/pkg/handler"
)

var Version = "v0.1.0"

type App struct {
	broker   *broker.AnsibleBroker
	args     Args
	config   Config
	dao      *dao.Dao
	log      *Log
	registry ansibleapp.Registry
}

func CreateApp() App {
	var err error
	app := App{}

	// Writing directly to stderr because log has not been bootstrapped
	if app.args, err = CreateArgs(); err != nil {
		os.Stderr.WriteString("ERROR: Failed to validate input\n")
		os.Stderr.WriteString(err.Error() + "\n")
		ArgsUsage()
		os.Exit(127)
	}

	if app.args.Version {
		fmt.Println(Version)
		os.Exit(0)
	}

	fmt.Println("============================================================")
	fmt.Println("==           Starting Ansible Service Broker...           ==")
	fmt.Println("============================================================")

	if app.config, err = CreateConfig(app.args.ConfigFile); err != nil {
		os.Stderr.WriteString("ERROR: Failed to read config file\n")
		os.Stderr.WriteString(err.Error())
		os.Exit(1)
	}

	if app.log, err = NewLog(app.config.Log); err != nil {
		os.Stderr.WriteString("ERROR: Failed to initialize logger\n")
		os.Stderr.WriteString(err.Error())
		os.Exit(1)
	}

	app.log.Debug("Connecting Dao")
	if app.dao, err = dao.NewDao(app.config.Dao, app.log.Logger); err != nil {
		app.log.Error("Failed to initialize Dao\n")
		app.log.Error(err.Error())
		os.Exit(1)
	}

	app.log.Debug("Connecting Registry")
	if app.registry, err = ansibleapp.NewRegistry(
		app.config.Registry, app.log.Logger,
	); err != nil {
		app.log.Error("Failed to initialize Dao\n")
		app.log.Error(err.Error())
		os.Exit(1)
	}

	////////////////////////////////////////////////////////////
	// HACK, TODO: Ugly way to configure concrete specifics for a DockerHubRegistry
	// Need to come up with a better way to handle this.
	////////////////////////////////////////////////////////////
	if app.config.Registry.Name == "dockerhub" {
		v, _ := app.registry.(*ansibleapp.DockerHubRegistry)
		v.ScriptsDir = app.args.ScriptsDir
	}
	////////////////////////////////////////////////////////////

	app.log.Debug("Creating AnsibleBroker")
	if app.broker, err = broker.NewAnsibleBroker(
		app.dao, app.log.Logger, app.config.Openshift, app.registry,
	); err != nil {
		app.log.Error("Failed to create AnsibleBroker\n")
		app.log.Error(err.Error())
		os.Exit(1)
	}

	return app
}

func (a *App) Start() {
	a.log.Notice("Ansible Service Broker Started")
	a.log.Notice("Listening on http://localhost:1338")
	http.ListenAndServe(":1338", handler.NewHandler(a.broker))
}
<commit_msg>Print error when HTTP server can't start (#31)<commit_after>package app

import (
	"fmt"
	"net/http"
	"os"

	"github.com/fusor/ansible-service-broker/pkg/ansibleapp"
	"github.com/fusor/ansible-service-broker/pkg/broker"
	"github.com/fusor/ansible-service-broker/pkg/dao"
	"github.com/fusor/ansible-service-broker/pkg/handler"
)

var Version = "v0.1.0"

type App struct {
	broker   *broker.AnsibleBroker
	args     Args
	config   Config
	dao      *dao.Dao
	log      *Log
	registry ansibleapp.Registry
}

func CreateApp() App {
	var err error
	app := App{}

	// Writing directly to stderr because log has not been bootstrapped
	if app.args, err = CreateArgs(); err != nil {
		os.Stderr.WriteString("ERROR: Failed to validate input\n")
		os.Stderr.WriteString(err.Error() + "\n")
		ArgsUsage()
		os.Exit(127)
	}

	if app.args.Version {
		fmt.Println(Version)
		os.Exit(0)
	}

	fmt.Println("============================================================")
	fmt.Println("==           Starting Ansible Service Broker...           ==")
	fmt.Println("============================================================")

	if app.config, err = CreateConfig(app.args.ConfigFile); err != nil {
		os.Stderr.WriteString("ERROR: Failed to read config file\n")
		os.Stderr.WriteString(err.Error())
		os.Exit(1)
	}

	if app.log, err = NewLog(app.config.Log); err != nil {
		os.Stderr.WriteString("ERROR: Failed to initialize logger\n")
		os.Stderr.WriteString(err.Error())
		os.Exit(1)
	}

	app.log.Debug("Connecting Dao")
	if app.dao, err = dao.NewDao(app.config.Dao, app.log.Logger); err != nil {
		app.log.Error("Failed to initialize Dao\n")
		app.log.Error(err.Error())
		os.Exit(1)
	}

	app.log.Debug("Connecting Registry")
	if app.registry, err = ansibleapp.NewRegistry(
		app.config.Registry, app.log.Logger,
	); err != nil {
		app.log.Error("Failed to initialize Registry\n")
		app.log.Error(err.Error())
		os.Exit(1)
	}

	////////////////////////////////////////////////////////////
	// HACK, TODO: Ugly way to configure concrete specifics for a DockerHubRegistry
	// Need to come up with a better way to handle this.
	////////////////////////////////////////////////////////////
	if app.config.Registry.Name == "dockerhub" {
		v, _ := app.registry.(*ansibleapp.DockerHubRegistry)
		v.ScriptsDir = app.args.ScriptsDir
	}
	////////////////////////////////////////////////////////////

	app.log.Debug("Creating AnsibleBroker")
	if app.broker, err = broker.NewAnsibleBroker(
		app.dao, app.log.Logger, app.config.Openshift, app.registry,
	); err != nil {
		app.log.Error("Failed to create AnsibleBroker\n")
		app.log.Error(err.Error())
		os.Exit(1)
	}

	return app
}

func (a *App) Start() {
	a.log.Notice("Ansible Service Broker Started")
	a.log.Notice("Listening on http://localhost:1338")
	err := http.ListenAndServe(":1338", handler.NewHandler(a.broker))
	if err != nil {
		a.log.Error("Failed to start HTTP server")
		a.log.Error(err.Error())
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package cli

import (
	"log"

	"github.com/michaelorr/goodall/pkg/agent"
	"github.com/michaelorr/goodall/pkg/db"
	"github.com/michaelorr/goodall/pkg/server"
)

func Run() int {
	c := parseArgs()

	conn, err := db.Open(c.DBPath)
	if err != nil {
		log.Println(err)
		return 1
	}
	err = db.Init(conn)
	if err != nil {
		log.Println(err)
		return 2
	}

	go agent.Run(conn, c.MetricIntervalMs, c.RetentionMin)
	go server.Run(conn, c.HTTPPort)

	return <-ret_val
}
<commit_msg>accidentally left a reference to channel that had been refactored away<commit_after>package cli

import (
	"log"

	"github.com/michaelorr/goodall/pkg/agent"
	"github.com/michaelorr/goodall/pkg/db"
	"github.com/michaelorr/goodall/pkg/server"
)

func Run() int {
	c := parseArgs()

	conn, err := db.Open(c.DBPath)
	if err != nil {
		log.Println(err)
		return 1
	}
	err = db.Init(conn)
	if err != nil {
		log.Println(err)
		return 2
	}

	go agent.Run(conn, c.MetricIntervalMs, c.RetentionMin)
	go server.Run(conn, c.HTTPPort)

	select {} // Block forever
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"io"
	"os"

	"github.com/ClusterHQ/dvol/pkg/api"
	"github.com/spf13/cobra"
)

func NewCmdLog(out io.Writer) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "log",
		Short: "Show log",
		Run: func(cmd *cobra.Command, args []string) {
			if err := outputLog(out); err != nil {
				fmt.Fprintln(os.Stderr, err.Error())
				os.Exit(1)
			}
		},
	}
	return cmd
}

func outputLog(out io.Writer) error {
	dvol := api.NewDvolAPI(dvolAPIOptions)
	activeVolume, err := dvol.ActiveVolume()
	if err != nil {
		return err
	}

	activeBranch, err := dvol.ActiveBranch(activeVolume)
	if err != nil {
		return err
	}

	commits, err := dvol.ListCommits(activeVolume, activeBranch)
	if err != nil {
		return err
	}
	for i := len(commits) - 1; i >= 0; i-- {
		commit := commits[i]
		fmt.Fprintf(out, "commit %s\n", commit.Id)
		fmt.Fprintln(out, "Author: Who knows <mystery@person>")
		fmt.Fprint(out, "Date: Whenever\n\n")
		fmt.Fprintf(out, "    %s\n\n", commit.Message)
	}
	return nil
}
<commit_msg>Use Fprint instead of Fprintln for consistency.<commit_after>package cmd

import (
	"fmt"
	"io"
	"os"

	"github.com/ClusterHQ/dvol/pkg/api"
	"github.com/spf13/cobra"
)

func NewCmdLog(out io.Writer) *cobra.Command {
	cmd := &cobra.Command{
		Use:   "log",
		Short: "Show log",
		Run: func(cmd *cobra.Command, args []string) {
			if err := outputLog(out); err != nil {
				fmt.Fprintln(os.Stderr, err.Error())
				os.Exit(1)
			}
		},
	}
	return cmd
}

func outputLog(out io.Writer) error {
	dvol := api.NewDvolAPI(dvolAPIOptions)
	activeVolume, err := dvol.ActiveVolume()
	if err != nil {
		return err
	}

	activeBranch, err := dvol.ActiveBranch(activeVolume)
	if err != nil {
		return err
	}

	commits, err := dvol.ListCommits(activeVolume, activeBranch)
	if err != nil {
		return err
	}
	for i := len(commits) - 1; i >= 0; i-- {
		commit := commits[i]
		fmt.Fprintf(out, "commit %s\n", commit.Id)
		fmt.Fprint(out, "Author: Who knows <mystery@person>\n")
		fmt.Fprint(out, "Date: Whenever\n\n")
		fmt.Fprintf(out, "    %s\n\n", commit.Message)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2017 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

// Package git provides helper functions for working with git repositories.
package git

import (
	"fmt"
	"os"
	"time"

	"github.com/google/syzkaller/pkg/osutil"
)

const timeout = time.Hour // timeout for all git invocations

// Poll checkouts the specified repository/branch in dir.
// This involves fetching/resetting/cloning as necessary to recover from all possible problems.
// Returns hash of the HEAD commit in the specified branch.
func Poll(dir, repo, branch string) (string, error) {
	osutil.RunCmd(timeout, dir, "git", "reset", "--hard")
	if _, err := osutil.RunCmd(timeout, dir, "git", "fetch", "--no-tags", "--depth=", "1"); err != nil {
		if err := os.RemoveAll(dir); err != nil {
			return "", fmt.Errorf("failed to remove repo dir: %v", err)
		}
		if err := os.MkdirAll(dir, 0700); err != nil {
			return "", fmt.Errorf("failed to create repo dir: %v", err)
		}
		args := []string{
			"clone",
			repo,
			"--no-tags",
			"--depth", "1",
			"--single-branch",
			"--branch", branch,
			dir,
		}
		if _, err := osutil.RunCmd(timeout, "", "git", args...); err != nil {
			return "", err
		}
	}
	if _, err := osutil.RunCmd(timeout, dir, "git", "checkout", branch); err != nil {
		return "", err
	}
	return HeadCommit(dir)
}

// HeadCommit returns hash of the HEAD commit of the current branch of git repository in dir.
func HeadCommit(dir string) (string, error) {
	output, err := osutil.RunCmd(timeout, dir, "git", "log", "--pretty=format:'%H'", "-n", "1")
	if err != nil {
		return "", err
	}
	if len(output) != 0 && output[len(output)-1] == '\n' {
		output = output[:len(output)-1]
	}
	if len(output) != 0 && output[0] == '\'' && output[len(output)-1] == '\'' {
		output = output[1 : len(output)-1]
	}
	if len(output) != 40 {
		return "", fmt.Errorf("unexpected git log output, want commit hash: %q", output)
	}
	return string(output), nil
}
<commit_msg>pkg/git: don't use --no-tags with clone<commit_after>// Copyright 2017 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

// Package git provides helper functions for working with git repositories.
package git

import (
	"fmt"
	"os"
	"time"

	"github.com/google/syzkaller/pkg/osutil"
)

const timeout = time.Hour // timeout for all git invocations

// Poll checkouts the specified repository/branch in dir.
// This involves fetching/resetting/cloning as necessary to recover from all possible problems.
// Returns hash of the HEAD commit in the specified branch.
func Poll(dir, repo, branch string) (string, error) {
	osutil.RunCmd(timeout, dir, "git", "reset", "--hard")
	if _, err := osutil.RunCmd(timeout, dir, "git", "fetch", "--no-tags", "--depth=", "1"); err != nil {
		if err := os.RemoveAll(dir); err != nil {
			return "", fmt.Errorf("failed to remove repo dir: %v", err)
		}
		if err := os.MkdirAll(dir, 0700); err != nil {
			return "", fmt.Errorf("failed to create repo dir: %v", err)
		}
		args := []string{
			"clone",
			repo,
			"--depth", "1",
			"--single-branch",
			"--branch", branch,
			dir,
		}
		if _, err := osutil.RunCmd(timeout, "", "git", args...); err != nil {
			return "", err
		}
	}
	if _, err := osutil.RunCmd(timeout, dir, "git", "checkout", branch); err != nil {
		return "", err
	}
	return HeadCommit(dir)
}

// HeadCommit returns hash of the HEAD commit of the current branch of git repository in dir.
func HeadCommit(dir string) (string, error) {
	output, err := osutil.RunCmd(timeout, dir, "git", "log", "--pretty=format:'%H'", "-n", "1")
	if err != nil {
		return "", err
	}
	if len(output) != 0 && output[len(output)-1] == '\n' {
		output = output[:len(output)-1]
	}
	if len(output) != 0 && output[0] == '\'' && output[len(output)-1] == '\'' {
		output = output[1 : len(output)-1]
	}
	if len(output) != 40 {
		return "", fmt.Errorf("unexpected git log output, want commit hash: %q", output)
	}
	return string(output), nil
}
<|endoftext|>


<commit_before>package pak

import (
	"bytes"
	"crypto/md5"
	"errors"
	"fmt"
	"io"
	"path"
	"strconv"
	"text/template"

	"github.com/cafebazaar/kupak/pkg/util"

	"github.com/ghodss/yaml"
)

func validateProperties(properties []Property) error {
	nameMap := make(map[string]bool)
	for i := range properties {
		if _, has := nameMap[properties[i].Name]; has {
			return errors.New("Duplicated property")
		}
		// validating types
		switch properties[i].Type {
		case "int":
		case "bool":
		case "string":
			// TODO validate the default value and other type specification
			_ = "ok"
		default:
			return errors.New("Specified type is not valid")
		}
	}
	return nil
}

// ID returns unique id for this pak
func (p *Pak) ID() string {
	md5er := md5.New()
	io.WriteString(md5er, p.URL)
	return fmt.Sprintf("%x", md5er.Sum(nil))
}

// receives a directory in which the resources can be found
func (p *Pak) fetchAndMakeTemplates(baseURL string) error {
	p.Templates = make([]*template.Template, len(p.ResourceURLs))
	for i := range p.ResourceURLs {
		url := util.JoinURL(baseURL, p.ResourceURLs[i])
		if !util.Relative(p.ResourceURLs[i]) {
			url = p.ResourceURLs[i]
		}

		data, err := util.FetchURL(url)
		if err != nil {
			return err
		}
		t := template.New(p.ResourceURLs[i])
		t.Delims("$(", ")")
		resourceTemplate, err := t.Parse(string(data))
		if err != nil {
			return err
		}
		p.Templates[i] = resourceTemplate
	}
	return nil
}

// ValidateValues validates given values with corresponding properties
// given values should be contain defaults - use MergeValuesWithDefaults before
// passing values to this function
func (p *Pak) ValidateValues(values map[string]interface{}) error {
	// check all required values are given and their values are ok
	for i := range p.Properties {
		v, has := values[p.Properties[i].Name]
		if !has {
			return errors.New("required property '" + p.Properties[i].Name + "' is not specified")
		}

		ok := false
		switch p.Properties[i].Type {
		case "string":
			_, ok = v.(string)
		case "int":
			_, ok = v.(int)
		case "bool":
			_, ok = v.(bool)
		}
		if !ok {
			return fmt.Errorf("value \"%v\" for property \"%s\" is not correct", v, p.Properties[i].Name)
		}
	}
	return nil
}

// ExecuteTemplates generate resources of a pak with given values
func (p *Pak) ExecuteTemplates(values map[string]interface{}) ([][]byte, error) {
	// merge default values
	err := p.AddDefaultValues(values)
	if err != nil {
		return nil, err
	}

	p.normalizeValues(values)

	err = p.ValidateValues(values)
	if err != nil {
		return nil, err
	}
	outputs := make([][]byte, len(p.Templates))
	for i := range p.Templates {
		buffer := &bytes.Buffer{}

		if err := p.Templates[i].Execute(buffer, values); err != nil {
			return nil, err
		}
		outputs[i] = buffer.Bytes()
	}
	return outputs, nil
}

// AddDefaultValues add default values for properties that not exists in values
func (p *Pak) AddDefaultValues(values map[string]interface{}) error {
	for i := range p.Properties {
		_, ok := values[p.Properties[i].Name]
		if !ok {
			values[p.Properties[i].Name] = p.Properties[i].Default
		}
	}
	return nil
}

// normalizeValues tries to convert default values to their specified type
// like yes, true, 1, ... to boolean true
// this should be called before ValidateValues and after AddDefaultValues
func (p *Pak) normalizeValues(values map[string]interface{}) {
	for i := range p.Properties {
		v, _ := values[p.Properties[i].Name]
		value := fmt.Sprintf("%v", v)
		switch p.Properties[i].Type {
		case "int":
			n, err := strconv.Atoi(value)
			if err == nil {
				values[p.Properties[i].Name] = n
			}
		case "bool":
			b, err := util.StringToBool(value)
			if err == nil {
				values[p.Properties[i].Name] = b
			}
		case "string":
			fallthrough
		default:
			values[p.Properties[i].Name] = v
		}
	}
}

// FromURL reads a pak.yaml file and fetches all the resources files
func FromURL(url string) (*Pak, error) {
	data, err := util.FetchURL(url)
	if err != nil {
		return nil, err
	}
	pak := Pak{}
	if err := yaml.Unmarshal(data, &pak); err != nil {
		return nil, err
	}
	if err := validateProperties(pak.Properties); err != nil {
		return nil, err
	}
	if err := pak.fetchAndMakeTemplates(path.Dir(url)); err != nil {
		return nil, err
	}
	return &pak, nil
}
<commit_msg>bad behaviour - quick fix<commit_after>package pak

import (
	"bytes"
	"crypto/md5"
	"errors"
	"fmt"
	"io"
	"strconv"
	"text/template"

	"github.com/cafebazaar/kupak/pkg/util"

	"github.com/ghodss/yaml"
)

func validateProperties(properties []Property) error {
	nameMap := make(map[string]bool)
	for i := range properties {
		if _, has := nameMap[properties[i].Name]; has {
			return errors.New("Duplicated property")
		}
		// validating types
		switch properties[i].Type {
		case "int":
		case "bool":
		case "string":
			// TODO validate the default value and other type specification
			_ = "ok"
		default:
			return errors.New("Specified type is not valid")
		}
	}
	return nil
}

// ID returns unique id for this pak
func (p *Pak) ID() string {
	md5er := md5.New()
	io.WriteString(md5er, p.URL)
	return fmt.Sprintf("%x", md5er.Sum(nil))
}

// receives a directory in which the resources can be found
func (p *Pak) fetchAndMakeTemplates(baseURL string) error {
	p.Templates = make([]*template.Template, len(p.ResourceURLs))
	for i := range p.ResourceURLs {
		url := util.JoinURL(baseURL, p.ResourceURLs[i])
		if !util.Relative(p.ResourceURLs[i]) {
			url = p.ResourceURLs[i]
		}

		data, err := util.FetchURL(url)
		if err != nil {
			return err
		}
		t := template.New(p.ResourceURLs[i])
		t.Delims("$(", ")")
		resourceTemplate, err := t.Parse(string(data))
		if err != nil {
			return err
		}
		p.Templates[i] = resourceTemplate
	}
	return nil
}

// ValidateValues validates given values with corresponding properties
// given values should be contain defaults - use MergeValuesWithDefaults before
// passing values to this function
func (p *Pak) ValidateValues(values map[string]interface{}) error {
	// check all required values are given and their values are ok
	for i := range p.Properties {
		v, has := values[p.Properties[i].Name]
		if !has {
			return errors.New("required property '" + p.Properties[i].Name + "' is not specified")
		}

		ok := false
		switch p.Properties[i].Type {
		case "string":
			_, ok = v.(string)
		case "int":
			_, ok = v.(int)
		case "bool":
			_, ok = v.(bool)
		}
		if !ok {
			return fmt.Errorf("value \"%v\" for property \"%s\" is not correct", v, p.Properties[i].Name)
		}
	}
	return nil
}

// ExecuteTemplates generate resources of a pak with given values
func (p *Pak) ExecuteTemplates(values map[string]interface{}) ([][]byte, error) {
	// merge default values
	err := p.AddDefaultValues(values)
	if err != nil {
		return nil, err
	}

	p.normalizeValues(values)

	err = p.ValidateValues(values)
	if err != nil {
		return nil, err
	}
	outputs := make([][]byte, len(p.Templates))
	for i := range p.Templates {
		buffer := &bytes.Buffer{}

		if err := p.Templates[i].Execute(buffer, values); err != nil {
			return nil, err
		}
		outputs[i] = buffer.Bytes()
	}
	return outputs, nil
}

// AddDefaultValues add default values for properties that not exists in values
func (p *Pak) AddDefaultValues(values map[string]interface{}) error {
	for i := range p.Properties {
		_, ok := values[p.Properties[i].Name]
		if !ok {
			values[p.Properties[i].Name] = p.Properties[i].Default
		}
	}
	return nil
}

// normalizeValues tries to convert default values to their specified type
// like yes, true, 1, ... to boolean true
// this should be called before ValidateValues and after AddDefaultValues
func (p *Pak) normalizeValues(values map[string]interface{}) {
	for i := range p.Properties {
		v, _ := values[p.Properties[i].Name]
		value := fmt.Sprintf("%v", v)
		switch p.Properties[i].Type {
		case "int":
			n, err := strconv.Atoi(value)
			if err == nil {
				values[p.Properties[i].Name] = n
			}
		case "bool":
			b, err := util.StringToBool(value)
			if err == nil {
				values[p.Properties[i].Name] = b
			}
		case "string":
			fallthrough
		default:
			values[p.Properties[i].Name] = v
		}
	}
}

// FromURL reads a pak.yaml file and fetches all the resources files
func FromURL(url string) (*Pak, error) {
	data, err := util.FetchURL(url)
	if err != nil {
		return nil, err
	}
	pak := Pak{}
	if err := yaml.Unmarshal(data, &pak); err != nil {
		return nil, err
	}
	if err := validateProperties(pak.Properties); err != nil {
		return nil, err
	}
	if err := pak.fetchAndMakeTemplates(url); err != nil {
		return nil, err
	}
	return &pak, nil
}
<|endoftext|>


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

// Package php contains PHP buildpack library code.
package php

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/GoogleCloudPlatform/buildpacks/pkg/appengine"
	"github.com/GoogleCloudPlatform/buildpacks/pkg/cache"
	"github.com/GoogleCloudPlatform/buildpacks/pkg/env"
	gcp "github.com/GoogleCloudPlatform/buildpacks/pkg/gcpbuildpack"
	"github.com/buildpacks/libcnb"
)

const (
	// composerJSON is the name of the Composer package descriptor file.
	composerJSON = "composer.json"
	// composerLock is the name of the Composer lock file.
	composerLock = "composer.lock"
	// Vendor is the name of the Composer vendor directory.
	Vendor = "vendor"

	phpVersionKey     = "php_version"
	dependencyHashKey = "dependency_hash"

	composerVersionKey = "php"

	// PHPIni is the content of the php.ini config file
	PHPIni = `
; Copyright 2022 Google Inc.
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
;     http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.

expose_php = Off
memory_limit = -1
max_execution_time = 0

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Error handling and logging, based on php.ini-production. ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
display_errors = Off
display_startup_errors = Off
log_errors = On
log_errors_max_len = 0
ignore_repeated_errors = Off
ignore_repeated_source = Off
html_errors = Off
zend.assertions = -1
;; Enable maximum file sizes up to Front-End limits.
upload_max_filesize = 32M
post_max_size = 32M
`
)

type composerScriptsJSON struct {
	GCPBuild string `json:"gcp-build"`
}

// ComposerJSON represents the contents of a composer.json file.
type ComposerJSON struct {
	Require map[string]string   `json:"require"`
	Scripts composerScriptsJSON `json:"scripts"`
}

// SupportsAppEngineApis is a function that returns true if App Engine API access is enabled
func SupportsAppEngineApis(ctx *gcp.Context) (bool, error) {
	if os.Getenv(env.Runtime) == "php55" {
		return true, nil
	}

	return appengine.ApisEnabled(ctx)
}

// ReadComposerJSON returns the deserialized composer.json from the given dir. Empty dir uses the current working directory.
func ReadComposerJSON(dir string) (*ComposerJSON, error) {
	f := filepath.Join(dir, composerJSON)
	rawcjs, err := ioutil.ReadFile(f)
	if err != nil {
		return nil, gcp.InternalErrorf("reading %s: %v", composerJSON, err)
	}

	var cjs ComposerJSON
	if err := json.Unmarshal(rawcjs, &cjs); err != nil {
		return nil, gcp.UserErrorf("unmarshalling %s: %v", composerJSON, err)
	}
	return &cjs, nil
}

// version returns the installed version of PHP.
func version(ctx *gcp.Context) (string, error) {
	result, err := ctx.Exec([]string{"php", "-r", "echo PHP_VERSION;"})
	if err != nil {
		return "", err
	}
	return result.Stdout, nil
}

// checkCache checks whether cached dependencies exist and match.
func checkCache(ctx *gcp.Context, l *libcnb.Layer, opts ...cache.Option) (bool, error) {
	currentPHPVersion, err := version(ctx)
	if err != nil {
		return false, err
	}
	opts = append(opts, cache.WithStrings(currentPHPVersion))
	currentDependencyHash, err := cache.Hash(ctx, opts...)
	if err != nil {
		return false, fmt.Errorf("computing dependency hash: %v", err)
	}

	// Perform install, skipping if the dependency hash matches existing metadata.
	metaDependencyHash := ctx.GetMetadata(l, dependencyHashKey)
	ctx.Debugf("Current dependency hash: %q", currentDependencyHash)
	ctx.Debugf("  Cache dependency hash: %q", metaDependencyHash)
	if currentDependencyHash == metaDependencyHash {
		ctx.Logf("Dependencies cache hit, skipping installation.")
		return true, nil
	}

	if metaDependencyHash == "" {
		ctx.Debugf("No metadata found from a previous build, skipping cache.")
	}
	ctx.Logf("Installing application dependencies.")

	// Update the layer metadata.
	ctx.SetMetadata(l, dependencyHashKey, currentDependencyHash)
	ctx.SetMetadata(l, phpVersionKey, currentPHPVersion)

	return false, nil
}

// composerInstall runs `composer install` with the given flags.
func composerInstall(ctx *gcp.Context, flags []string) error {
	cmd := append([]string{"composer", "install"}, flags...)
	if _, err := ctx.Exec(cmd, gcp.WithUserAttribution); err != nil {
		return err
	}
	return nil
}

// ComposerInstall runs `composer install`, using the cache iff a lock file is present.
// It creates a layer, so it returns the layer so that the caller may further modify it
// if they desire.
func ComposerInstall(ctx *gcp.Context, cacheTag string) (*libcnb.Layer, error) {
	var flags []string
	if composerArgs := os.Getenv(env.ComposerArgsEnv); composerArgs != "" {
		flags = strings.Split(composerArgs, " ")
	} else {
		// We don't install dev dependencies (i.e. we pass --no-dev to composer) because doing so has caused
		// problems for customers in the past. For more information see these links:
		//   https://github.com/GoogleCloudPlatform/php-docs-samples/issues/736
		//   https://github.com/GoogleCloudPlatform/runtimes-common/pull/763
		//   https://github.com/GoogleCloudPlatform/runtimes-common/commit/6c4970f609d80f9436ac58ae272cfcc6bcd57143
		flags = []string{"--no-dev", "--no-progress", "--no-interaction", "--optimize-autoloader"}
	}

	if err := ctx.RemoveAll(Vendor); err != nil {
		return nil, err
	}
	l, err := ctx.Layer("composer", gcp.CacheLayer)
	if err != nil {
		return nil, fmt.Errorf("creating layer: %w", err)
	}
	layerVendor := filepath.Join(l.Path, Vendor)

	composerLockExists, err := ctx.FileExists(composerLock)
	if err != nil {
		return nil, err
	}
	// If there's no composer.lock then don't attempt to cache. We'd have to cache using composer.json,
	// which could result in outdated dependencies if the version constraints in composer.json resolve
	// to newer versions in the future.
	if !composerLockExists {
		ctx.Logf("*** Improve build performance by generating and committing %s.", composerLock)
		if err := composerInstall(ctx, flags); err != nil {
			return nil, err
		}
		return l, nil
	}

	cached, err := checkCache(ctx, l, cache.WithFiles(composerJSON, composerLock))
	if err != nil {
		return l, fmt.Errorf("checking cache: %w", err)
	}
	if cached {
		ctx.CacheHit(cacheTag)

		// PHP expects the vendor/ directory to be in the application directory.
		if _, err := ctx.Exec([]string{"cp", "--archive", layerVendor, Vendor}, gcp.WithUserTimingAttribution); err != nil {
			return nil, err
		}
	} else {
		ctx.CacheMiss(cacheTag)
		// Clear layer so we don't end up with outdated dependencies (e.g. something was removed from composer.json).
		if err := ctx.ClearLayer(l); err != nil {
			return nil, fmt.Errorf("clearing layer %q: %w", l.Name, err)
		}
		if err := composerInstall(ctx, flags); err != nil {
			return nil, err
		}

		// Ensure vendor exists even if no dependencies were installed.
		if err := ctx.MkdirAll(Vendor, 0755); err != nil {
			return nil, err
		}
		if _, err := ctx.Exec([]string{"cp", "--archive", Vendor, layerVendor}, gcp.WithUserTimingAttribution); err != nil {
			return nil, err
		}
	}

	return l, nil
}

// ComposerRequire runs `composer require` with the given packages. It expects packages to
// be specified as `composer require` would expect them on the command line, for example
// "myorg/mypackage:^0.7". It does no caching.
func ComposerRequire(ctx *gcp.Context, packages []string) error {
	cmd := append([]string{"composer", "require", "--no-progress", "--no-interaction"}, packages...)
	if _, err := ctx.Exec(cmd, gcp.WithUserAttribution); err != nil {
		return err
	}
	return nil
}

// ExtractVersion extracts the php version from the environment, composer.json.
func ExtractVersion(ctx *gcp.Context) (string, error) {
	// get the runtime version from env.RuntimeVersion
	if v := os.Getenv(env.RuntimeVersion); v != "" {
		ctx.Logf("Using runtime version from %s: %s", env.RuntimeVersion, v)
		return v, nil
	}

	// get the runtime version from the composer.json file
	composerFilePath := filepath.Join(ctx.ApplicationRoot(), composerJSON)
	composerFileExists, err := ctx.FileExists(composerFilePath)
	if err != nil {
		return "", err
	}
	if composerFileExists {
		v, err := composerFileVersion(ctx)
		if err != nil {
			return "", err
		}
		if v != "" {
			ctx.Logf("Using php version from %s %s: %s", composerJSON, composerVersionKey, v)
			return v, nil
		}
	}

	return "", nil
}

// composerFileVersion extracts the version number from composer.json. returns an error in
// case the version cannot be read.
func composerFileVersion(ctx *gcp.Context) (string, error) {
	cjs, err := ReadComposerJSON(ctx.ApplicationRoot())
	if err != nil {
		return "", err
	}

	// check if composer json has specified php version.
	v, ok := cjs.Require[composerVersionKey]
	if !ok {
		ctx.Logf("composer.json exists but does not specify a php version")
		return "", nil
	}

	return v, nil
}
<commit_msg>Fix bug in which layer metadata was not set after clearing a layer<commit_after>// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package php contains PHP buildpack library code.
package php

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/GoogleCloudPlatform/buildpacks/pkg/appengine"
	"github.com/GoogleCloudPlatform/buildpacks/pkg/cache"
	"github.com/GoogleCloudPlatform/buildpacks/pkg/env"
	gcp "github.com/GoogleCloudPlatform/buildpacks/pkg/gcpbuildpack"
	"github.com/buildpacks/libcnb"
)

const (
	// composerJSON is the name of the Composer package descriptor file.
	composerJSON = "composer.json"
	// composerLock is the name of the Composer lock file.
	composerLock = "composer.lock"
	// Vendor is the name of the Composer vendor directory.
	Vendor = "vendor"

	phpVersionKey     = "php_version"
	dependencyHashKey = "dependency_hash"

	composerVersionKey = "php"

	// PHPIni is the content of the php.ini config file
	PHPIni = `
; Copyright 2022 Google Inc.
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
;     http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.

expose_php = Off
memory_limit = -1
max_execution_time = 0

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Error handling and logging, based on php.ini-production. ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
display_errors = Off
display_startup_errors = Off
log_errors = On
log_errors_max_len = 0
ignore_repeated_errors = Off
ignore_repeated_source = Off
html_errors = Off
zend.assertions = -1
;; Enable maximum file sizes up to Front-End limits.
upload_max_filesize = 32M
post_max_size = 32M
`
)

type composerScriptsJSON struct {
	GCPBuild string `json:"gcp-build"`
}

// ComposerJSON represents the contents of a composer.json file.
type ComposerJSON struct {
	Require map[string]string   `json:"require"`
	Scripts composerScriptsJSON `json:"scripts"`
}

// SupportsAppEngineApis is a function that returns true if App Engine API access is enabled
func SupportsAppEngineApis(ctx *gcp.Context) (bool, error) {
	if os.Getenv(env.Runtime) == "php55" {
		return true, nil
	}

	return appengine.ApisEnabled(ctx)
}

// ReadComposerJSON returns the deserialized composer.json from the given dir. Empty dir uses the current working directory.
func ReadComposerJSON(dir string) (*ComposerJSON, error) {
	f := filepath.Join(dir, composerJSON)
	rawcjs, err := ioutil.ReadFile(f)
	if err != nil {
		return nil, gcp.InternalErrorf("reading %s: %v", composerJSON, err)
	}

	var cjs ComposerJSON
	if err := json.Unmarshal(rawcjs, &cjs); err != nil {
		return nil, gcp.UserErrorf("unmarshalling %s: %v", composerJSON, err)
	}
	return &cjs, nil
}

// version returns the installed version of PHP.
func version(ctx *gcp.Context) (string, error) {
	result, err := ctx.Exec([]string{"php", "-r", "echo PHP_VERSION;"})
	if err != nil {
		return "", err
	}
	return result.Stdout, nil
}

// composerDependencyHash computes a hash of composer metadata files that is used to check whether
// or not cached PHP dependencies in a layer should be re-installed.
func composerDependencyHash(ctx *gcp.Context) (string, error) {
	currentPHPVersion, err := version(ctx)
	if err != nil {
		return "", err
	}
	currentDependencyHash, err := cache.Hash(ctx, cache.WithFiles(composerJSON, composerLock), cache.WithStrings(currentPHPVersion))
	if err != nil {
		return "", fmt.Errorf("computing dependency hash: %v", err)
	}
	return currentDependencyHash, nil
}

// composerInstall runs `composer install` with the given flags.
func composerInstall(ctx *gcp.Context, flags []string) error {
	cmd := append([]string{"composer", "install"}, flags...)
	if _, err := ctx.Exec(cmd, gcp.WithUserAttribution); err != nil {
		return err
	}
	return nil
}

// ComposerInstall runs `composer install`, using the cache iff a lock file is present.
// It creates a layer, so it returns the layer so that the caller may further modify it
// if they desire.
func ComposerInstall(ctx *gcp.Context, cacheTag string) (*libcnb.Layer, error) {
	var flags []string
	if composerArgs := os.Getenv(env.ComposerArgsEnv); composerArgs != "" {
		flags = strings.Split(composerArgs, " ")
	} else {
		// We don't install dev dependencies (i.e. we pass --no-dev to composer) because doing so has caused
		// problems for customers in the past. For more information see these links:
		//   https://github.com/GoogleCloudPlatform/php-docs-samples/issues/736
		//   https://github.com/GoogleCloudPlatform/runtimes-common/pull/763
		//   https://github.com/GoogleCloudPlatform/runtimes-common/commit/6c4970f609d80f9436ac58ae272cfcc6bcd57143
		flags = []string{"--no-dev", "--no-progress", "--no-interaction", "--optimize-autoloader"}
	}

	if err := ctx.RemoveAll(Vendor); err != nil {
		return nil, err
	}
	l, err := ctx.Layer("composer", gcp.CacheLayer)
	if err != nil {
		return nil, fmt.Errorf("creating layer: %w", err)
	}
	layerVendor := filepath.Join(l.Path, Vendor)

	composerLockExists, err := ctx.FileExists(composerLock)
	if err != nil {
		return nil, err
	}
	// If there's no composer.lock then don't attempt to cache. We'd have to cache using composer.json,
	// which could result in outdated dependencies if the version constraints in composer.json resolve
	// to newer versions in the future.
	if !composerLockExists {
		ctx.Logf("*** Improve build performance by generating and committing %s.", composerLock)
		if err := composerInstall(ctx, flags); err != nil {
			return nil, err
		}
		return l, nil
	}

	currentDependencyHash, err := composerDependencyHash(ctx)
	if err != nil {
		return l, fmt.Errorf("checking cache: %w", err)
	}

	// Perform install, skipping if the dependency hash matches existing metadata.
	metaDependencyHash := ctx.GetMetadata(l, dependencyHashKey)
	ctx.Debugf("Current dependency hash: %q", currentDependencyHash)
	ctx.Debugf("  Cache dependency hash: %q", metaDependencyHash)

	if currentDependencyHash == metaDependencyHash {
		ctx.Logf("Dependencies cache hit, skipping installation.")
		ctx.CacheHit(cacheTag)

		// PHP expects the vendor/ directory to be in the application directory.
		if _, err := ctx.Exec([]string{"cp", "--archive", layerVendor, Vendor}, gcp.WithUserTimingAttribution); err != nil {
			return nil, err
		}
	} else {
		ctx.CacheMiss(cacheTag)
		if metaDependencyHash == "" {
			ctx.Debugf("No metadata found from a previous build, skipping cache.")
		}
		ctx.Logf("Installing application dependencies.")
		// Clear layer so we don't end up with outdated dependencies (e.g. something was removed from composer.json).
		if err := ctx.ClearLayer(l); err != nil {
			return nil, fmt.Errorf("clearing layer %q: %w", l.Name, err)
		}
		if err := composerInstall(ctx, flags); err != nil {
			return nil, err
		}

		// Update the layer metadata.
		ctx.SetMetadata(l, dependencyHashKey, currentDependencyHash)

		// Ensure vendor exists even if no dependencies were installed.
		if err := ctx.MkdirAll(Vendor, 0755); err != nil {
			return nil, err
		}
		if _, err := ctx.Exec([]string{"cp", "--archive", Vendor, layerVendor}, gcp.WithUserTimingAttribution); err != nil {
			return nil, err
		}
	}

	return l, nil
}

// ComposerRequire runs `composer require` with the given packages. It expects packages to
// be specified as `composer require` would expect them on the command line, for example
// "myorg/mypackage:^0.7". It does no caching.
func ComposerRequire(ctx *gcp.Context, packages []string) error {
	cmd := append([]string{"composer", "require", "--no-progress", "--no-interaction"}, packages...)
	if _, err := ctx.Exec(cmd, gcp.WithUserAttribution); err != nil {
		return err
	}
	return nil
}

// ExtractVersion extracts the php version from the environment, composer.json.
func ExtractVersion(ctx *gcp.Context) (string, error) {
	// get the runtime version from env.RuntimeVersion
	if v := os.Getenv(env.RuntimeVersion); v != "" {
		ctx.Logf("Using runtime version from %s: %s", env.RuntimeVersion, v)
		return v, nil
	}

	// get the runtime version from the composer.json file
	composerFilePath := filepath.Join(ctx.ApplicationRoot(), composerJSON)
	composerFileExists, err := ctx.FileExists(composerFilePath)
	if err != nil {
		return "", err
	}
	if composerFileExists {
		v, err := composerFileVersion(ctx)
		if err != nil {
			return "", err
		}
		if v != "" {
			ctx.Logf("Using php version from %s %s: %s", composerJSON, composerVersionKey, v)
			return v, nil
		}
	}

	return "", nil
}

// composerFileVersion extracts the version number from composer.json. returns an error in
// case the version cannot be read.
func composerFileVersion(ctx *gcp.Context) (string, error) {
	cjs, err := ReadComposerJSON(ctx.ApplicationRoot())
	if err != nil {
		return "", err
	}

	// check if composer json has specified php version.
	v, ok := cjs.Require[composerVersionKey]
	if !ok {
		ctx.Logf("composer.json exists but does not specify a php version")
		return "", nil
	}

	return v, nil
}
<|endoftext|>