<commit_before>package api_test

import (
	"errors"
	"io/ioutil"
	"net/http"

	"code.cloudfoundry.org/lager"
	"github.com/aws/aws-sdk-go/aws/awserr"
	awssecretsmanager "github.com/aws/aws-sdk-go/service/secretsmanager"
	"github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface"
	awsssm "github.com/aws/aws-sdk-go/service/ssm"
	"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
	"github.com/concourse/atc/api/accessor/accessorfakes"
	"github.com/concourse/atc/creds/credhub"
	"github.com/concourse/atc/creds/secretsmanager"
	"github.com/concourse/atc/creds/ssm"
	"github.com/concourse/atc/creds/vault"
	vaultapi "github.com/hashicorp/vault/api"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/ghttp"
)

type MockSsmService struct {
	ssmiface.SSMAPI

	stubGetParameter func(input *awsssm.GetParameterInput) (*awsssm.GetParameterOutput, error)
}

func (m *MockSsmService) GetParameter(input *awsssm.GetParameterInput) (*awsssm.GetParameterOutput, error) {
	return m.stubGetParameter(input)
}

type MockSecretsManagerService struct {
	secretsmanageriface.SecretsManagerAPI

	stubGetSecretValue func(input *awssecretsmanager.GetSecretValueInput) (*awssecretsmanager.GetSecretValueOutput, error)
}

func (m *MockSecretsManagerService) GetSecretValue(input *awssecretsmanager.GetSecretValueInput) (*awssecretsmanager.GetSecretValueOutput, error) {
	return m.stubGetSecretValue(input)
}

var _ = Describe("Pipelines API", func() {
	Describe("GET /api/v1/info", func() {
		var response *http.Response

		JustBeforeEach(func() {
			var err error

			response, err = client.Get(server.URL + "/api/v1/info")
			Expect(err).NotTo(HaveOccurred())
		})

		It("returns Content-Type 'application/json'", func() {
			Expect(response.Header.Get("Content-Type")).To(Equal("application/json"))
		})

		It("contains the version", func() {
			body, err := ioutil.ReadAll(response.Body)
			Expect(err).NotTo(HaveOccurred())

			Expect(body).To(MatchJSON(`{
				"version": "1.2.3",
				"worker_version": "4.5.6"
			}`))
		})
	})

	Describe("GET /api/v1/info/creds", func() {
		var (
			response   *http.Response
			fakeaccess *accessorfakes.FakeAccess
			credServer *ghttp.Server
			body       []byte
		)

		BeforeEach(func() {
			fakeaccess = new(accessorfakes.FakeAccess)
		})

		JustBeforeEach(func() {
			fakeAccessor.CreateReturns(fakeaccess)
		})

		JustBeforeEach(func() {
			req, err := http.NewRequest("GET", server.URL+"/api/v1/info/creds", nil)
			Expect(err).NotTo(HaveOccurred())
			req.Header.Set("Content-Type", "application/json")

			response, err = client.Do(req)
			Expect(err).NotTo(HaveOccurred())

			Expect(response.StatusCode).To(Equal(http.StatusOK))
			Expect(response.Header.Get("Content-Type")).To(Equal("application/json"))

			body, err = ioutil.ReadAll(response.Body)
			Expect(err).NotTo(HaveOccurred())
		})

		Context("SSM", func() {
			var mockService MockSsmService

			BeforeEach(func() {
				fakeaccess.IsAuthenticatedReturns(true)
				fakeaccess.IsAdminReturns(true)

				ssmAccess := ssm.NewSsm(lager.NewLogger("ssm_test"), &mockService, "alpha", "bogus", nil)
				ssmManager := &ssm.SsmManager{
					AwsAccessKeyID:         "",
					AwsSecretAccessKey:     "",
					AwsSessionToken:        "",
					AwsRegion:              "blah",
					PipelineSecretTemplate: "pipeline-secret-template",
					TeamSecretTemplate:     "team-secret-template",
					Ssm:                    ssmAccess,
				}

				credsManagers["ssm"] = ssmManager
			})

			Context("returns configured ssm manager", func() {
				Context("get ssm manager info returns error", func() {

					BeforeEach(func() {
						mockService.stubGetParameter = func(input *awsssm.GetParameterInput) (*awsssm.GetParameterOutput, error) {
							return nil, errors.New("some error occured")
						}
					})

					It("includes the error in json response", func() {
						Expect(body).To(MatchJSON(`{
          "ssm": {
						"aws_region": "blah",
						"health": {
							"error": "some error occured",
							"method": "GetParameter"
						},
						"pipeline_secret_template": "pipeline-secret-template",
						"team_secret_template": "team-secret-template"
          }
        }`))
					})
				})

				Context("get ssm manager info", func() {

					BeforeEach(func() {
						mockService.stubGetParameter = func(input *awsssm.GetParameterInput) (*awsssm.GetParameterOutput, error) {
							return nil, awserr.New(awsssm.ErrCodeParameterNotFound, "dontcare", nil)
						}
					})

					It("includes the ssm health info in json response", func() {
						Expect(body).To(MatchJSON(`{
          "ssm": {
						"aws_region": "blah",
						"health": {
							"response": {
								"status": "UP"
							},
							"method": "GetParameter"
						},
						"pipeline_secret_template": "pipeline-secret-template",
						"team_secret_template": "team-secret-template"
          }
        }`))
					})
				})
			})
		})

		Context("vault", func() {
			BeforeEach(func() {
				fakeaccess.IsAuthenticatedReturns(true)
				fakeaccess.IsAdminReturns(true)

				authConfig := vault.AuthConfig{
					Backend:       "backend-server",
					BackendMaxTTL: 20,
					RetryMax:      5,
					RetryInitial:  2,
				}

				tls := vault.TLS{
					CACert:     "",
					ServerName: "server-name",
				}

				credServer = ghttp.NewServer()
				vaultManager := &vault.VaultManager{
					URL:        credServer.URL(),
					PathPrefix: "testpath",
					Cache:      false,
					MaxLease:   60,
					TLS:        tls,
					Auth:       authConfig,
				}

				err := vaultManager.Init(lager.NewLogger("test"))
				Expect(err).ToNot(HaveOccurred())

				credsManagers["vault"] = vaultManager

				credServer.RouteToHandler("GET", "/v1/sys/health", ghttp.RespondWithJSONEncoded(
					http.StatusOK,
					&vaultapi.HealthResponse{
						Initialized:                true,
						Sealed:                     false,
						Standby:                    false,
						ReplicationPerformanceMode: "foo",
						ReplicationDRMode:          "blah",
						ServerTimeUTC:              0,
						Version:                    "1.0.0",
					},
				))
			})

			Context("get vault health info returns error", func() {

				BeforeEach(func() {
					credServer.RouteToHandler("GET", "/v1/sys/health", ghttp.RespondWithJSONEncoded(
						http.StatusInternalServerError,
						"some error occured",
					))
				})

				It("returns configured creds manager with error", func() {
					Expect(body).To(MatchJSON(`{
					"vault": {
						"url": "` + credServer.URL() + `",
						"path_prefix": "testpath",
						"cache": false,
						"max_lease": 60,
						"ca_cert": "",
						"server_name": "server-name",
						"auth_backend": "backend-server",
						"auth_max_ttl": 20,
						"auth_retry_max": 5,
						"auth_retry_initial": 2,
						"health": {
							"error": "Error making API request.\n\nURL: GET ` + credServer.URL() + `/v1/sys/health?drsecondarycode=299\u0026sealedcode=299\u0026standbycode=299\u0026uninitcode=299\nCode: 500. Raw Message:\n\n\"some error occured\"",
							"method": "/v1/sys/health"
						}
					}
				}`))
				})
			})

			Context("get vault health info", func() {

				BeforeEach(func() {
					credServer.RouteToHandler("GET", "/v1/sys/health", ghttp.RespondWithJSONEncoded(
						http.StatusOK,
						&vaultapi.HealthResponse{
							Initialized:                true,
							Sealed:                     false,
							Standby:                    false,
							ReplicationPerformanceMode: "foo",
							ReplicationDRMode:          "blah",
							ServerTimeUTC:              0,
							Version:                    "1.0.0",
						},
					))
				})

				It("returns configured creds manager", func() {
					Expect(body).To(MatchJSON(`{
          "vault": {
            "url": "` + credServer.URL() + `",
            "path_prefix": "testpath",
						"cache": false,
						"max_lease": 60,
            "ca_cert": "",
            "server_name": "server-name",
						"auth_backend": "backend-server",
						"auth_max_ttl": 20,
						"auth_retry_max": 5,
						"auth_retry_initial": 2,
						"health": {
							"response": {
                  "initialized": true,
                  "sealed": false,
                  "standby": false,
                  "replication_performance_mode": "foo",
                  "replication_dr_mode": "blah",
                  "server_time_utc": 0,
                  "version": "1.0.0"
                },
                "method": "/v1/sys/health"
						}
          }
        }`))
				})
			})
		})

		Context("credhub", func() {
			var (
				tls credhub.TLS
				uaa credhub.UAA
			)

			BeforeEach(func() {
				fakeaccess.IsAuthenticatedReturns(true)
				fakeaccess.IsAdminReturns(true)

				tls = credhub.TLS{
					CACerts: []string{},
				}
				uaa = credhub.UAA{
					ClientId:     "client-id",
					ClientSecret: "client-secret",
				}
			})

			Context("get credhub help info succeeds", func() {
				BeforeEach(func() {
					credServer = ghttp.NewServer()
					credServer.RouteToHandler("GET", "/health", ghttp.RespondWithJSONEncoded(
						http.StatusOK, map[string]string{
							"status": "UP",
						},
					))

					credhubManager := &credhub.CredHubManager{
						URL:        credServer.URL(),
						PathPrefix: "some-prefix",
						TLS:        tls,
						UAA:        uaa,
						Client:     &credhub.LazyCredhub{},
					}

					credsManagers["credhub"] = credhubManager
				})

				It("returns configured creds manager with response", func() {
					Expect(body).To(MatchJSON(`{
					"credhub": {
						"url": "` + credServer.URL() + `",
						"ca_certs": [],
						"health": {
							"response": {
								"status": "UP"
							},
							"method": "/health"
						},
						"path_prefix": "some-prefix",
						"uaa_client_id": "client-id"
						}
					}`))
				})
			})

			Context("get credhub health info returns error", func() {

				BeforeEach(func() {
					credhubManager := &credhub.CredHubManager{
						URL:        "http://wrong.inexistent.tld",
						PathPrefix: "some-prefix",
						TLS:        tls,
						UAA:        uaa,
						Client:     &credhub.LazyCredhub{},
					}

					credsManagers["credhub"] = credhubManager
				})

				It("returns configured creds manager with error", func() {
					Expect(body).To(MatchJSON(`{
					"credhub": {
						"url": "http://wrong.inexistent.tld",
						"path_prefix": "some-prefix",
						"ca_certs": [],
						"uaa_client_id": "client-id",
						"health": {
							"error": "Get http://wrong.inexistent.tld/health: dial tcp: lookup wrong.inexistent.tld: no such host",
							"method": "/health"
						}
					}
				}`))
				})
			})
		})

		Context("SecretsManager", func() {
			var mockService MockSecretsManagerService

			BeforeEach(func() {
				fakeaccess.IsAuthenticatedReturns(true)
				fakeaccess.IsAdminReturns(true)

				secretsManagerAccess := secretsmanager.NewSecretsManager(lager.NewLogger("ssm_test"), &mockService, "alpha", "bogus", nil)

				secretsManager := &secretsmanager.Manager{
					AwsAccessKeyID:         "",
					AwsSecretAccessKey:     "",
					AwsSessionToken:        "",
					AwsRegion:              "blah",
					PipelineSecretTemplate: "pipeline-secret-template",
					TeamSecretTemplate:     "team-secret-template",
					SecretManager:          secretsManagerAccess,
				}

				credsManagers["secretsmanager"] = secretsManager
			})

			Context("returns configured secretsmanager manager", func() {
				Context("get secretsmanager info returns error", func() {

					BeforeEach(func() {
						mockService.stubGetSecretValue = func(input *awssecretsmanager.GetSecretValueInput) (*awssecretsmanager.GetSecretValueOutput, error) {
							return nil, errors.New("some error occurred")
						}
					})

					It("includes the error in json response", func() {
						Expect(body).To(MatchJSON(`{
					"secretsmanager": {
						"aws_region": "blah",
						"pipeline_secret_template": "pipeline-secret-template",
						"team_secret_template": "team-secret-template",
						"health": {
							"error": "some error occurred",
							"method": "GetSecretValue"
						}
					}
				}`))
					})

				})

				Context("get secretsmanager info", func() {

					BeforeEach(func() {
						mockService.stubGetSecretValue = func(input *awssecretsmanager.GetSecretValueInput) (*awssecretsmanager.GetSecretValueOutput, error) {

							return nil, awserr.New(awssecretsmanager.ErrCodeResourceNotFoundException, "dontcare", nil)
						}
					})

					It("include sthe secretsmanager info in json response", func() {
						Expect(body).To(MatchJSON(`{
					"secretsmanager": {
						"aws_region": "blah",
						"pipeline_secret_template": "pipeline-secret-template",
						"team_secret_template": "team-secret-template",
						"health": {
							"response": {
								"status": "UP"
							},
							"method": "GetSecretValue"
						}
					}
				}`))
					})
				})
			})

		})
	})
})
<commit_msg>fix test error of credhub<commit_after>package api_test

import (
	"encoding/json"
	"errors"
	"io/ioutil"
	"net/http"

	"code.cloudfoundry.org/lager"
	"github.com/aws/aws-sdk-go/aws/awserr"
	awssecretsmanager "github.com/aws/aws-sdk-go/service/secretsmanager"
	"github.com/aws/aws-sdk-go/service/secretsmanager/secretsmanageriface"
	awsssm "github.com/aws/aws-sdk-go/service/ssm"
	"github.com/aws/aws-sdk-go/service/ssm/ssmiface"
	"github.com/concourse/atc/api/accessor/accessorfakes"
	"github.com/concourse/atc/creds/credhub"
	"github.com/concourse/atc/creds/secretsmanager"
	"github.com/concourse/atc/creds/ssm"
	"github.com/concourse/atc/creds/vault"
	vaultapi "github.com/hashicorp/vault/api"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/ghttp"
)

type MockSsmService struct {
	ssmiface.SSMAPI

	stubGetParameter func(input *awsssm.GetParameterInput) (*awsssm.GetParameterOutput, error)
}

func (m *MockSsmService) GetParameter(input *awsssm.GetParameterInput) (*awsssm.GetParameterOutput, error) {
	return m.stubGetParameter(input)
}

type MockSecretsManagerService struct {
	secretsmanageriface.SecretsManagerAPI

	stubGetSecretValue func(input *awssecretsmanager.GetSecretValueInput) (*awssecretsmanager.GetSecretValueOutput, error)
}

func (m *MockSecretsManagerService) GetSecretValue(input *awssecretsmanager.GetSecretValueInput) (*awssecretsmanager.GetSecretValueOutput, error) {
	return m.stubGetSecretValue(input)
}

var _ = Describe("Pipelines API", func() {
	Describe("GET /api/v1/info", func() {
		var response *http.Response

		JustBeforeEach(func() {
			var err error

			response, err = client.Get(server.URL + "/api/v1/info")
			Expect(err).NotTo(HaveOccurred())
		})

		It("returns Content-Type 'application/json'", func() {
			Expect(response.Header.Get("Content-Type")).To(Equal("application/json"))
		})

		It("contains the version", func() {
			body, err := ioutil.ReadAll(response.Body)
			Expect(err).NotTo(HaveOccurred())

			Expect(body).To(MatchJSON(`{
				"version": "1.2.3",
				"worker_version": "4.5.6"
			}`))
		})
	})

	Describe("GET /api/v1/info/creds", func() {
		var (
			response   *http.Response
			fakeaccess *accessorfakes.FakeAccess
			credServer *ghttp.Server
			body       []byte
		)

		BeforeEach(func() {
			fakeaccess = new(accessorfakes.FakeAccess)
		})

		JustBeforeEach(func() {
			fakeAccessor.CreateReturns(fakeaccess)
		})

		JustBeforeEach(func() {
			req, err := http.NewRequest("GET", server.URL+"/api/v1/info/creds", nil)
			Expect(err).NotTo(HaveOccurred())
			req.Header.Set("Content-Type", "application/json")

			response, err = client.Do(req)
			Expect(err).NotTo(HaveOccurred())

			Expect(response.StatusCode).To(Equal(http.StatusOK))
			Expect(response.Header.Get("Content-Type")).To(Equal("application/json"))

			body, err = ioutil.ReadAll(response.Body)
			Expect(err).NotTo(HaveOccurred())
		})

		Context("SSM", func() {
			var mockService MockSsmService

			BeforeEach(func() {
				fakeaccess.IsAuthenticatedReturns(true)
				fakeaccess.IsAdminReturns(true)

				ssmAccess := ssm.NewSsm(lager.NewLogger("ssm_test"), &mockService, "alpha", "bogus", nil)
				ssmManager := &ssm.SsmManager{
					AwsAccessKeyID:         "",
					AwsSecretAccessKey:     "",
					AwsSessionToken:        "",
					AwsRegion:              "blah",
					PipelineSecretTemplate: "pipeline-secret-template",
					TeamSecretTemplate:     "team-secret-template",
					Ssm:                    ssmAccess,
				}

				credsManagers["ssm"] = ssmManager
			})

			Context("returns configured ssm manager", func() {
				Context("get ssm manager info returns error", func() {

					BeforeEach(func() {
						mockService.stubGetParameter = func(input *awsssm.GetParameterInput) (*awsssm.GetParameterOutput, error) {
							return nil, errors.New("some error occured")
						}
					})

					It("includes the error in json response", func() {
						Expect(body).To(MatchJSON(`{
          "ssm": {
						"aws_region": "blah",
						"health": {
							"error": "some error occured",
							"method": "GetParameter"
						},
						"pipeline_secret_template": "pipeline-secret-template",
						"team_secret_template": "team-secret-template"
          }
        }`))
					})
				})

				Context("get ssm manager info", func() {

					BeforeEach(func() {
						mockService.stubGetParameter = func(input *awsssm.GetParameterInput) (*awsssm.GetParameterOutput, error) {
							return nil, awserr.New(awsssm.ErrCodeParameterNotFound, "dontcare", nil)
						}
					})

					It("includes the ssm health info in json response", func() {
						Expect(body).To(MatchJSON(`{
          "ssm": {
						"aws_region": "blah",
						"health": {
							"response": {
								"status": "UP"
							},
							"method": "GetParameter"
						},
						"pipeline_secret_template": "pipeline-secret-template",
						"team_secret_template": "team-secret-template"
          }
        }`))
					})
				})
			})
		})

		Context("vault", func() {
			BeforeEach(func() {
				fakeaccess.IsAuthenticatedReturns(true)
				fakeaccess.IsAdminReturns(true)

				authConfig := vault.AuthConfig{
					Backend:       "backend-server",
					BackendMaxTTL: 20,
					RetryMax:      5,
					RetryInitial:  2,
				}

				tls := vault.TLS{
					CACert:     "",
					ServerName: "server-name",
				}

				credServer = ghttp.NewServer()
				vaultManager := &vault.VaultManager{
					URL:        credServer.URL(),
					PathPrefix: "testpath",
					Cache:      false,
					MaxLease:   60,
					TLS:        tls,
					Auth:       authConfig,
				}

				err := vaultManager.Init(lager.NewLogger("test"))
				Expect(err).ToNot(HaveOccurred())

				credsManagers["vault"] = vaultManager

				credServer.RouteToHandler("GET", "/v1/sys/health", ghttp.RespondWithJSONEncoded(
					http.StatusOK,
					&vaultapi.HealthResponse{
						Initialized:                true,
						Sealed:                     false,
						Standby:                    false,
						ReplicationPerformanceMode: "foo",
						ReplicationDRMode:          "blah",
						ServerTimeUTC:              0,
						Version:                    "1.0.0",
					},
				))
			})

			Context("get vault health info returns error", func() {

				BeforeEach(func() {
					credServer.RouteToHandler("GET", "/v1/sys/health", ghttp.RespondWithJSONEncoded(
						http.StatusInternalServerError,
						"some error occured",
					))
				})

				It("returns configured creds manager with error", func() {
					Expect(body).To(MatchJSON(`{
					"vault": {
						"url": "` + credServer.URL() + `",
						"path_prefix": "testpath",
						"cache": false,
						"max_lease": 60,
						"ca_cert": "",
						"server_name": "server-name",
						"auth_backend": "backend-server",
						"auth_max_ttl": 20,
						"auth_retry_max": 5,
						"auth_retry_initial": 2,
						"health": {
							"error": "Error making API request.\n\nURL: GET ` + credServer.URL() + `/v1/sys/health?drsecondarycode=299\u0026sealedcode=299\u0026standbycode=299\u0026uninitcode=299\nCode: 500. Raw Message:\n\n\"some error occured\"",
							"method": "/v1/sys/health"
						}
					}
				}`))
				})
			})

			Context("get vault health info", func() {

				BeforeEach(func() {
					credServer.RouteToHandler("GET", "/v1/sys/health", ghttp.RespondWithJSONEncoded(
						http.StatusOK,
						&vaultapi.HealthResponse{
							Initialized:                true,
							Sealed:                     false,
							Standby:                    false,
							ReplicationPerformanceMode: "foo",
							ReplicationDRMode:          "blah",
							ServerTimeUTC:              0,
							Version:                    "1.0.0",
						},
					))
				})

				It("returns configured creds manager", func() {
					Expect(body).To(MatchJSON(`{
          "vault": {
            "url": "` + credServer.URL() + `",
            "path_prefix": "testpath",
						"cache": false,
						"max_lease": 60,
            "ca_cert": "",
            "server_name": "server-name",
						"auth_backend": "backend-server",
						"auth_max_ttl": 20,
						"auth_retry_max": 5,
						"auth_retry_initial": 2,
						"health": {
							"response": {
                  "initialized": true,
                  "sealed": false,
                  "standby": false,
                  "replication_performance_mode": "foo",
                  "replication_dr_mode": "blah",
                  "server_time_utc": 0,
                  "version": "1.0.0"
                },
                "method": "/v1/sys/health"
						}
          }
        }`))
				})
			})
		})

		Context("credhub", func() {
			var (
				tls credhub.TLS
				uaa credhub.UAA
			)

			BeforeEach(func() {
				fakeaccess.IsAuthenticatedReturns(true)
				fakeaccess.IsAdminReturns(true)

				tls = credhub.TLS{
					CACerts: []string{},
				}
				uaa = credhub.UAA{
					ClientId:     "client-id",
					ClientSecret: "client-secret",
				}
			})

			Context("get credhub help info succeeds", func() {
				BeforeEach(func() {
					credServer = ghttp.NewServer()
					credServer.RouteToHandler("GET", "/health", ghttp.RespondWithJSONEncoded(
						http.StatusOK, map[string]string{
							"status": "UP",
						},
					))

					credhubManager := &credhub.CredHubManager{
						URL:        credServer.URL(),
						PathPrefix: "some-prefix",
						TLS:        tls,
						UAA:        uaa,
						Client:     &credhub.LazyCredhub{},
					}

					credsManagers["credhub"] = credhubManager
				})

				It("returns configured creds manager with response", func() {
					Expect(body).To(MatchJSON(`{
					"credhub": {
						"url": "` + credServer.URL() + `",
						"ca_certs": [],
						"health": {
							"response": {
								"status": "UP"
							},
							"method": "/health"
						},
						"path_prefix": "some-prefix",
						"uaa_client_id": "client-id"
						}
					}`))
				})
			})

			Context("get credhub health info returns error", func() {
				type responseSkeleton struct {
					CredHub struct {
						Url     string   `json:"url"`
						CACerts []string `json:"ca_certs"`
						Health  struct {
							Error    string `json:"error"`
							Response struct {
								Status string `json:"status"`
							} `json:"response"`
							Method string `json:"method"`
						} `json:"health"`
						PathPrefix  string `json:"path_prefix"`
						UAAClientId string `json:"uaa_client_id"`
					} `json:"credhub"`
				}

				BeforeEach(func() {
					credhubManager := &credhub.CredHubManager{
						URL:        "http://wrong.inexistent.tld",
						PathPrefix: "some-prefix",
						TLS:        tls,
						UAA:        uaa,
						Client:     &credhub.LazyCredhub{},
					}

					credsManagers["credhub"] = credhubManager
				})

				It("returns configured creds manager with error", func() {
					var parsedResponse responseSkeleton

					err := json.Unmarshal(body, &parsedResponse)
					Expect(err).ToNot(HaveOccurred())

					Expect(parsedResponse.CredHub.Url).To(Equal("http://wrong.inexistent.tld"))
					Expect(parsedResponse.CredHub.CACerts).To(BeEmpty())
					Expect(parsedResponse.CredHub.PathPrefix).To(Equal("some-prefix"))
					Expect(parsedResponse.CredHub.UAAClientId).To(Equal("client-id"))
					Expect(parsedResponse.CredHub.Health.Response).ToNot(BeNil())
					Expect(parsedResponse.CredHub.Health.Response.Status).To(BeEmpty())
					Expect(parsedResponse.CredHub.Health.Method).To(Equal("/health"))
					Expect(parsedResponse.CredHub.Health.Error).To(ContainSubstring("no such host"))
				})
			})
		})

		Context("SecretsManager", func() {
			var mockService MockSecretsManagerService

			BeforeEach(func() {
				fakeaccess.IsAuthenticatedReturns(true)
				fakeaccess.IsAdminReturns(true)

				secretsManagerAccess := secretsmanager.NewSecretsManager(lager.NewLogger("ssm_test"), &mockService, "alpha", "bogus", nil)

				secretsManager := &secretsmanager.Manager{
					AwsAccessKeyID:         "",
					AwsSecretAccessKey:     "",
					AwsSessionToken:        "",
					AwsRegion:              "blah",
					PipelineSecretTemplate: "pipeline-secret-template",
					TeamSecretTemplate:     "team-secret-template",
					SecretManager:          secretsManagerAccess,
				}

				credsManagers["secretsmanager"] = secretsManager
			})

			Context("returns configured secretsmanager manager", func() {
				Context("get secretsmanager info returns error", func() {

					BeforeEach(func() {
						mockService.stubGetSecretValue = func(input *awssecretsmanager.GetSecretValueInput) (*awssecretsmanager.GetSecretValueOutput, error) {
							return nil, errors.New("some error occurred")
						}
					})

					It("includes the error in json response", func() {
						Expect(body).To(MatchJSON(`{
					"secretsmanager": {
						"aws_region": "blah",
						"pipeline_secret_template": "pipeline-secret-template",
						"team_secret_template": "team-secret-template",
						"health": {
							"error": "some error occurred",
							"method": "GetSecretValue"
						}
					}
				}`))
					})

				})

				Context("get secretsmanager info", func() {

					BeforeEach(func() {
						mockService.stubGetSecretValue = func(input *awssecretsmanager.GetSecretValueInput) (*awssecretsmanager.GetSecretValueOutput, error) {

							return nil, awserr.New(awssecretsmanager.ErrCodeResourceNotFoundException, "dontcare", nil)
						}
					})

					It("include sthe secretsmanager info in json response", func() {
						Expect(body).To(MatchJSON(`{
					"secretsmanager": {
						"aws_region": "blah",
						"pipeline_secret_template": "pipeline-secret-template",
						"team_secret_template": "team-secret-template",
						"health": {
							"response": {
								"status": "UP"
							},
							"method": "GetSecretValue"
						}
					}
				}`))
					})
				})
			})

		})
	})
})
<|endoftext|>


<commit_before>// Copyright 2015 clair authors
//
// 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 v1

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"time"

	"github.com/coreos/clair/database"
	"github.com/coreos/clair/utils/types"
	"github.com/coreos/pkg/capnslog"
	"github.com/fernet/fernet-go"
)

var log = capnslog.NewPackageLogger("github.com/coreos/clair", "v1")

type Error struct {
	Message string `json:"Layer`
}

type Layer struct {
	Name             string    `json:"Name,omitempty"`
	Namespace        string    `json:"Namespace,omitempty"`
	Path             string    `json:"Path,omitempty"`
	ParentName       string    `json:"ParentName,omitempty"`
	Format           string    `json:"Format,omitempty"`
	IndexedByVersion int       `json:"IndexedByVersion,omitempty"`
	Features         []Feature `json:"Features,omitempty"`
}

func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabilities bool) Layer {
	layer := Layer{
		Name:             dbLayer.Name,
		IndexedByVersion: dbLayer.EngineVersion,
	}

	if dbLayer.Parent != nil {
		layer.ParentName = dbLayer.Parent.Name
	}

	if dbLayer.Namespace != nil {
		layer.Namespace = dbLayer.Namespace.Name
	}

	if withFeatures || withVulnerabilities && dbLayer.Features != nil {
		for _, dbFeatureVersion := range dbLayer.Features {
			feature := Feature{
				Name:      dbFeatureVersion.Feature.Name,
				Namespace: dbFeatureVersion.Feature.Namespace.Name,
				Version:   dbFeatureVersion.Version.String(),
				AddedBy:   dbFeatureVersion.AddedBy.Name,
			}

			for _, dbVuln := range dbFeatureVersion.AffectedBy {
				vuln := Vulnerability{
					Name:        dbVuln.Name,
					Namespace:   dbVuln.Namespace.Name,
					Description: dbVuln.Description,
					Link:        dbVuln.Link,
					Severity:    string(dbVuln.Severity),
					Metadata:    dbVuln.Metadata,
				}

				if dbVuln.FixedBy != types.MaxVersion {
					vuln.FixedBy = dbVuln.FixedBy.String()
				}
				feature.Vulnerabilities = append(feature.Vulnerabilities, vuln)
			}
			layer.Features = append(layer.Features, feature)
		}
	}

	return layer
}

type Vulnerability struct {
	Name        string                 `json:"Name,omitempty"`
	Namespace   string                 `json:"Namespace,omitempty"`
	Description string                 `json:"Description,omitempty"`
	Link        string                 `json:"Link,omitempty"`
	Severity    string                 `json:"Severity,omitempty"`
	Metadata    map[string]interface{} `json:"Metadata,omitempty"`
	FixedBy     string                 `json:"FixedBy,omitempty"`
	FixedIn     []Feature              `json:"FixedIn,omitempty"`
}

func (v Vulnerability) DatabaseModel() (database.Vulnerability, error) {
	severity := types.Priority(v.Severity)
	if !severity.IsValid() {
		return database.Vulnerability{}, errors.New("Invalid severity")
	}

	var dbFeatures []database.FeatureVersion
	for _, feature := range v.FixedIn {
		dbFeature, err := feature.DatabaseModel()
		if err != nil {
			return database.Vulnerability{}, err
		}

		dbFeatures = append(dbFeatures, dbFeature)
	}

	return database.Vulnerability{
		Name:        v.Name,
		Namespace:   database.Namespace{Name: v.Namespace},
		Description: v.Description,
		Link:        v.Link,
		Severity:    severity,
		Metadata:    v.Metadata,
		FixedIn:     dbFeatures,
	}, nil
}

func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability, withFixedIn bool) Vulnerability {
	vuln := Vulnerability{
		Name:        dbVuln.Name,
		Namespace:   dbVuln.Namespace.Name,
		Description: dbVuln.Description,
		Link:        dbVuln.Link,
		Severity:    string(dbVuln.Severity),
		Metadata:    dbVuln.Metadata,
	}

	if withFixedIn {
		for _, dbFeatureVersion := range dbVuln.FixedIn {
			vuln.FixedIn = append(vuln.FixedIn, FeatureFromDatabaseModel(dbFeatureVersion))
		}
	}

	return vuln
}

type Feature struct {
	Name            string          `json:"Name,omitempty"`
	Namespace       string          `json:"Namespace,omitempty"`
	Version         string          `json:"Version,omitempty"`
	Vulnerabilities []Vulnerability `json:"Vulnerabilities,omitempty"`
	AddedBy         string          `json:"AddedBy,omitempty"`
}

func FeatureFromDatabaseModel(dbFeatureVersion database.FeatureVersion) Feature {
	versionStr := dbFeatureVersion.Version.String()
	if versionStr == types.MaxVersion.String() {
		versionStr = "None"
	}

	return Feature{
		Name:      dbFeatureVersion.Feature.Name,
		Namespace: dbFeatureVersion.Feature.Namespace.Name,
		Version:   versionStr,
		AddedBy:   dbFeatureVersion.AddedBy.Name,
	}
}

func (f Feature) DatabaseModel() (database.FeatureVersion, error) {
	var version types.Version
	if f.Version == "None" {
		version = types.MaxVersion
	} else {
		var err error
		version, err = types.NewVersion(f.Version)
		if err != nil {
			return database.FeatureVersion{}, err
		}
	}

	return database.FeatureVersion{
		Feature: database.Feature{
			Name:      f.Name,
			Namespace: database.Namespace{Name: f.Namespace},
		},
		Version: version,
	}, nil
}

type Notification struct {
	Name     string                   `json:"Name,omitempty"`
	Created  string                   `json:"Created,omitempty"`
	Notified string                   `json:"Notified,omitempty"`
	Deleted  string                   `json:"Deleted,omitempty"`
	Limit    int                      `json:"Limit,omitempty"`
	Page     string                   `json:"Page,omitempty"`
	NextPage string                   `json:"NextPage,omitempty"`
	Old      *VulnerabilityWithLayers `json:"Old,omitempty"`
	New      *VulnerabilityWithLayers `json:"New,omitempty"`
}

func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotification, limit int, page, nextPage database.VulnerabilityNotificationPageNumber, key string) Notification {
	var oldVuln *VulnerabilityWithLayers
	if dbNotification.OldVulnerability != nil {
		v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.OldVulnerability)
		oldVuln = &v
	}

	var newVuln *VulnerabilityWithLayers
	if dbNotification.NewVulnerability != nil {
		v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.NewVulnerability)
		newVuln = &v
	}

	var nextPageStr string
	if nextPage != database.NoVulnerabilityNotificationPage {
		nextPageStr = pageNumberToToken(nextPage, key)
	}

	var created, notified, deleted string
	if !dbNotification.Created.IsZero() {
		created = fmt.Sprintf("%d", dbNotification.Created.Unix())
	}
	if !dbNotification.Notified.IsZero() {
		notified = fmt.Sprintf("%d", dbNotification.Notified.Unix())
	}
	if !dbNotification.Deleted.IsZero() {
		deleted = fmt.Sprintf("%d", dbNotification.Deleted.Unix())
	}

	// TODO(jzelinskie): implement "changed" key
	fmt.Println(dbNotification.Deleted.IsZero())
	return Notification{
		Name:     dbNotification.Name,
		Created:  created,
		Notified: notified,
		Deleted:  deleted,
		Limit:    limit,
		Page:     pageNumberToToken(page, key),
		NextPage: nextPageStr,
		Old:      oldVuln,
		New:      newVuln,
	}
}

type VulnerabilityWithLayers struct {
	Vulnerability                  *Vulnerability `json:"Vulnerability,omitempty"`
	LayersIntroducingVulnerability []string       `json:"LayersIntroducingVulnerability,omitempty"`
}

func VulnerabilityWithLayersFromDatabaseModel(dbVuln database.Vulnerability) VulnerabilityWithLayers {
	vuln := VulnerabilityFromDatabaseModel(dbVuln, true)

	var layers []string
	for _, layer := range dbVuln.LayersIntroducingVulnerability {
		layers = append(layers, layer.Name)
	}

	return VulnerabilityWithLayers{
		Vulnerability:                  &vuln,
		LayersIntroducingVulnerability: layers,
	}
}

type LayerEnvelope struct {
	Layer *Layer `json:"Layer,omitempty"`
	Error *Error `json:"Error,omitempty"`
}

type NamespaceEnvelope struct {
	Namespaces *[]string `json:"Namespaces,omitempty"`
	Error      *Error    `json:"Error,omitempty"`
}

type VulnerabilityEnvelope struct {
	Vulnerability *Vulnerability `json:"Vulnerability,omitempty"`
	Error         *Error         `json:"Error,omitempty"`
}

type NotificationEnvelope struct {
	Notification *Notification `json:"Notification,omitempty"`
	Error        *Error        `json:"Error,omitempty"`
}

type FeatureEnvelope struct {
	Feature  *Feature   `json:"Feature,omitempty"`
	Features *[]Feature `json:"Features,omitempty"`
	Error    *Error     `json:"Error,omitempty"`
}

func tokenToPageNumber(token, key string) (database.VulnerabilityNotificationPageNumber, error) {
	k, _ := fernet.DecodeKey(key)
	msg := fernet.VerifyAndDecrypt([]byte(token), time.Hour, []*fernet.Key{k})
	if msg == nil {
		return database.VulnerabilityNotificationPageNumber{}, errors.New("invalid or expired pagination token")
	}

	page := database.VulnerabilityNotificationPageNumber{}
	err := json.NewDecoder(bytes.NewBuffer(msg)).Decode(&page)
	return page, err
}

func pageNumberToToken(page database.VulnerabilityNotificationPageNumber, key string) string {
	var buf bytes.Buffer
	err := json.NewEncoder(&buf).Encode(page)
	if err != nil {
		log.Fatal("failed to encode VulnerabilityNotificationPageNumber")
	}

	k, _ := fernet.DecodeKey(key)
	tokenBytes, err := fernet.EncryptAndSign(buf.Bytes(), k)
	if err != nil {
		log.Fatal("failed to encrypt VulnerabilityNotificationpageNumber")
	}

	return string(tokenBytes)
}
<commit_msg>v1: pagination now deterministic<commit_after>// Copyright 2015 clair authors
//
// 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 v1

import (
	"errors"
	"fmt"
	"time"

	"github.com/coreos/clair/database"
	"github.com/coreos/clair/utils/types"
	"github.com/coreos/pkg/capnslog"
	"github.com/fernet/fernet-go"
)

var log = capnslog.NewPackageLogger("github.com/coreos/clair", "v1")

type Error struct {
	Message string `json:"Layer`
}

type Layer struct {
	Name             string    `json:"Name,omitempty"`
	Namespace        string    `json:"Namespace,omitempty"`
	Path             string    `json:"Path,omitempty"`
	ParentName       string    `json:"ParentName,omitempty"`
	Format           string    `json:"Format,omitempty"`
	IndexedByVersion int       `json:"IndexedByVersion,omitempty"`
	Features         []Feature `json:"Features,omitempty"`
}

func LayerFromDatabaseModel(dbLayer database.Layer, withFeatures, withVulnerabilities bool) Layer {
	layer := Layer{
		Name:             dbLayer.Name,
		IndexedByVersion: dbLayer.EngineVersion,
	}

	if dbLayer.Parent != nil {
		layer.ParentName = dbLayer.Parent.Name
	}

	if dbLayer.Namespace != nil {
		layer.Namespace = dbLayer.Namespace.Name
	}

	if withFeatures || withVulnerabilities && dbLayer.Features != nil {
		for _, dbFeatureVersion := range dbLayer.Features {
			feature := Feature{
				Name:      dbFeatureVersion.Feature.Name,
				Namespace: dbFeatureVersion.Feature.Namespace.Name,
				Version:   dbFeatureVersion.Version.String(),
				AddedBy:   dbFeatureVersion.AddedBy.Name,
			}

			for _, dbVuln := range dbFeatureVersion.AffectedBy {
				vuln := Vulnerability{
					Name:        dbVuln.Name,
					Namespace:   dbVuln.Namespace.Name,
					Description: dbVuln.Description,
					Link:        dbVuln.Link,
					Severity:    string(dbVuln.Severity),
					Metadata:    dbVuln.Metadata,
				}

				if dbVuln.FixedBy != types.MaxVersion {
					vuln.FixedBy = dbVuln.FixedBy.String()
				}
				feature.Vulnerabilities = append(feature.Vulnerabilities, vuln)
			}
			layer.Features = append(layer.Features, feature)
		}
	}

	return layer
}

type Vulnerability struct {
	Name        string                 `json:"Name,omitempty"`
	Namespace   string                 `json:"Namespace,omitempty"`
	Description string                 `json:"Description,omitempty"`
	Link        string                 `json:"Link,omitempty"`
	Severity    string                 `json:"Severity,omitempty"`
	Metadata    map[string]interface{} `json:"Metadata,omitempty"`
	FixedBy     string                 `json:"FixedBy,omitempty"`
	FixedIn     []Feature              `json:"FixedIn,omitempty"`
}

func (v Vulnerability) DatabaseModel() (database.Vulnerability, error) {
	severity := types.Priority(v.Severity)
	if !severity.IsValid() {
		return database.Vulnerability{}, errors.New("Invalid severity")
	}

	var dbFeatures []database.FeatureVersion
	for _, feature := range v.FixedIn {
		dbFeature, err := feature.DatabaseModel()
		if err != nil {
			return database.Vulnerability{}, err
		}

		dbFeatures = append(dbFeatures, dbFeature)
	}

	return database.Vulnerability{
		Name:        v.Name,
		Namespace:   database.Namespace{Name: v.Namespace},
		Description: v.Description,
		Link:        v.Link,
		Severity:    severity,
		Metadata:    v.Metadata,
		FixedIn:     dbFeatures,
	}, nil
}

func VulnerabilityFromDatabaseModel(dbVuln database.Vulnerability, withFixedIn bool) Vulnerability {
	vuln := Vulnerability{
		Name:        dbVuln.Name,
		Namespace:   dbVuln.Namespace.Name,
		Description: dbVuln.Description,
		Link:        dbVuln.Link,
		Severity:    string(dbVuln.Severity),
		Metadata:    dbVuln.Metadata,
	}

	if withFixedIn {
		for _, dbFeatureVersion := range dbVuln.FixedIn {
			vuln.FixedIn = append(vuln.FixedIn, FeatureFromDatabaseModel(dbFeatureVersion))
		}
	}

	return vuln
}

type Feature struct {
	Name            string          `json:"Name,omitempty"`
	Namespace       string          `json:"Namespace,omitempty"`
	Version         string          `json:"Version,omitempty"`
	Vulnerabilities []Vulnerability `json:"Vulnerabilities,omitempty"`
	AddedBy         string          `json:"AddedBy,omitempty"`
}

func FeatureFromDatabaseModel(dbFeatureVersion database.FeatureVersion) Feature {
	versionStr := dbFeatureVersion.Version.String()
	if versionStr == types.MaxVersion.String() {
		versionStr = "None"
	}

	return Feature{
		Name:      dbFeatureVersion.Feature.Name,
		Namespace: dbFeatureVersion.Feature.Namespace.Name,
		Version:   versionStr,
		AddedBy:   dbFeatureVersion.AddedBy.Name,
	}
}

func (f Feature) DatabaseModel() (database.FeatureVersion, error) {
	var version types.Version
	if f.Version == "None" {
		version = types.MaxVersion
	} else {
		var err error
		version, err = types.NewVersion(f.Version)
		if err != nil {
			return database.FeatureVersion{}, err
		}
	}

	return database.FeatureVersion{
		Feature: database.Feature{
			Name:      f.Name,
			Namespace: database.Namespace{Name: f.Namespace},
		},
		Version: version,
	}, nil
}

type Notification struct {
	Name     string                   `json:"Name,omitempty"`
	Created  string                   `json:"Created,omitempty"`
	Notified string                   `json:"Notified,omitempty"`
	Deleted  string                   `json:"Deleted,omitempty"`
	Limit    int                      `json:"Limit,omitempty"`
	Page     string                   `json:"Page,omitempty"`
	NextPage string                   `json:"NextPage,omitempty"`
	Old      *VulnerabilityWithLayers `json:"Old,omitempty"`
	New      *VulnerabilityWithLayers `json:"New,omitempty"`
}

func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotification, limit int, page, nextPage database.VulnerabilityNotificationPageNumber, key string) Notification {
	var oldVuln *VulnerabilityWithLayers
	if dbNotification.OldVulnerability != nil {
		v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.OldVulnerability)
		oldVuln = &v
	}

	var newVuln *VulnerabilityWithLayers
	if dbNotification.NewVulnerability != nil {
		v := VulnerabilityWithLayersFromDatabaseModel(*dbNotification.NewVulnerability)
		newVuln = &v
	}

	var nextPageStr string
	if nextPage != database.NoVulnerabilityNotificationPage {
		nextPageStr = pageNumberToToken(nextPage, key)
	}

	var created, notified, deleted string
	if !dbNotification.Created.IsZero() {
		created = fmt.Sprintf("%d", dbNotification.Created.Unix())
	}
	if !dbNotification.Notified.IsZero() {
		notified = fmt.Sprintf("%d", dbNotification.Notified.Unix())
	}
	if !dbNotification.Deleted.IsZero() {
		deleted = fmt.Sprintf("%d", dbNotification.Deleted.Unix())
	}

	// TODO(jzelinskie): implement "changed" key
	fmt.Println(dbNotification.Deleted.IsZero())
	return Notification{
		Name:     dbNotification.Name,
		Created:  created,
		Notified: notified,
		Deleted:  deleted,
		Limit:    limit,
		Page:     pageNumberToToken(page, key),
		NextPage: nextPageStr,
		Old:      oldVuln,
		New:      newVuln,
	}
}

type VulnerabilityWithLayers struct {
	Vulnerability                  *Vulnerability `json:"Vulnerability,omitempty"`
	LayersIntroducingVulnerability []string       `json:"LayersIntroducingVulnerability,omitempty"`
}

func VulnerabilityWithLayersFromDatabaseModel(dbVuln database.Vulnerability) VulnerabilityWithLayers {
	vuln := VulnerabilityFromDatabaseModel(dbVuln, true)

	var layers []string
	for _, layer := range dbVuln.LayersIntroducingVulnerability {
		layers = append(layers, layer.Name)
	}

	return VulnerabilityWithLayers{
		Vulnerability:                  &vuln,
		LayersIntroducingVulnerability: layers,
	}
}

type LayerEnvelope struct {
	Layer *Layer `json:"Layer,omitempty"`
	Error *Error `json:"Error,omitempty"`
}

type NamespaceEnvelope struct {
	Namespaces *[]string `json:"Namespaces,omitempty"`
	Error      *Error    `json:"Error,omitempty"`
}

type VulnerabilityEnvelope struct {
	Vulnerability *Vulnerability `json:"Vulnerability,omitempty"`
	Error         *Error         `json:"Error,omitempty"`
}

type NotificationEnvelope struct {
	Notification *Notification `json:"Notification,omitempty"`
	Error        *Error        `json:"Error,omitempty"`
}

type FeatureEnvelope struct {
	Feature  *Feature   `json:"Feature,omitempty"`
	Features *[]Feature `json:"Features,omitempty"`
	Error    *Error     `json:"Error,omitempty"`
}

func tokenToPageNumber(token, key string) (database.VulnerabilityNotificationPageNumber, error) {
	k, _ := fernet.DecodeKey(key)
	msg := fernet.VerifyAndDecrypt([]byte(token), time.Hour, []*fernet.Key{k})
	if msg == nil {
		return database.VulnerabilityNotificationPageNumber{}, errors.New("invalid or expired pagination token")
	}

	page := database.VulnerabilityNotificationPageNumber{}
	_, err := fmt.Sscanf(string(msg), "old:%d|new:%d", &page.OldVulnerability, &page.NewVulnerability)
	return page, err
}

func pageNumberToToken(page database.VulnerabilityNotificationPageNumber, key string) string {
	unencryptedToken := []byte(fmt.Sprintf("old:%d|new:%d", page.OldVulnerability, page.NewVulnerability))

	k, _ := fernet.DecodeKey(key)
	tokenBytes, err := fernet.EncryptAndSign(unencryptedToken, k)
	if err != nil {
		log.Fatal("failed to encrypt VulnerabilityNotificationpageNumber")
	}

	return string(tokenBytes)
}
<|endoftext|>


<commit_before>package conf

import (
	"os"
	"path"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	beego "github.com/astaxie/beego/logs"
)

// GOPATH contains the gopath of the env
var GOPATH string

// LogDir is the log directory for logs.json
var LogDir string

// LogFile name
var LogFile string

func init() {
	GOPATH = os.Getenv("GOPATH")
	if GOPATH == "" {
		GOPATH = defaultGOPATH()
	}

	appPath := GOPATH + "/src/github.com/snagles/docker-registry-manager/app"
	LogDir = appPath + "/logs/"
	LogFile = LogDir + "/log.json"

	// Create the log file if needed
	if _, err := os.Stat(LogFile); os.IsNotExist(err) {
		if _, err = os.Create(LogFile); err != nil {
			logrus.Fatal(err)
		}
	}

	// Setup logrus
	f, _ := os.OpenFile(LogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
	logrus.SetOutput(f)
	logrus.SetFormatter(&logrus.JSONFormatter{})
	logrus.AddHook(ContextHook{})

	// Setup Beego logging by registering the hook
	beego.Register("docker-registry-manager", NewBeegoHook)
	beego.SetLogger("docker-registry-manager", "")
	beego.EnableFuncCallDepth(true)
}

type registryLogger struct {
	Level int `json:"level"`
}

func NewBeegoHook() beego.Logger { return &registryLogger{Level: 0} }

// Init returns nothing since theres no additional config optios needed
func (rl *registryLogger) Init(jsonconfig string) error { return nil }

// Destroy is a empty method
func (rl *registryLogger) Destroy() {}

// Flush is a empty method
func (rl *registryLogger) Flush() {}

// WriteMsg will write the msg and level into es
func (rl *registryLogger) WriteMsg(when time.Time, msg string, level int) error {
	if level < int(logrus.GetLevel()) {
		return nil
	}

	switch level {
	// beego is reverse order
	case 0:
		logrus.Panic(msg)
	case 1:
		logrus.Panic(msg)
	case 2:
		logrus.Panic(msg)
	case 3:
		logrus.Error(msg)
	case 4:
		logrus.Warn(msg)
	case 5:
		logrus.Info(msg)
	case 6:
		logrus.Info(msg)
	case 7:
		logrus.Debug(msg)
	}
	return nil
}

type ContextHook struct{}

func (hook ContextHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (hook ContextHook) Fire(entry *logrus.Entry) error {
	pc := make([]uintptr, 3, 3)
	cnt := runtime.Callers(6, pc)

	for i := 0; i < cnt; i++ {
		fu := runtime.FuncForPC(pc[i] - 1)
		name := fu.Name()
		if !strings.Contains(name, "github.com/Sirupsen/logrus") {

			if strings.Contains(name, "registryLogger") {
				// Remove the prefix beego attaches
				r := regexp.MustCompile(`\[[A-Z]\] \[(.*):(.*)\] (.*)`)
				message := r.FindAllStringSubmatch(entry.Message, 1)

				// add the caller as a separate field
				entry.Data["file"] = message[0][1]
				line, _ := strconv.Atoi(message[0][2])
				entry.Data["line"] = line
				entry.Message = message[0][3]
				entry.Data["source"] = "beego"
				break
			} else {
				file, line := fu.FileLine(pc[i] - 1)
				entry.Data["file"] = path.Base(file)
				entry.Data["line"] = line
				entry.Data["source"] = "app"
				break
			}
		}

	}
	return nil
}

func defaultGOPATH() string {
	env := "HOME"
	if runtime.GOOS == "windows" {
		env = "USERPROFILE"
	} else if runtime.GOOS == "plan9" {
		env = "home"
	}
	if home := os.Getenv(env); home != "" {
		def := filepath.Join(home, "go")
		if filepath.Clean(def) == filepath.Clean(runtime.GOROOT()) {
			// Don't set the default GOPATH to GOROOT,
			// as that will trigger warnings from the go tool.
			return ""
		}
		return def
	}
	return ""
}
<commit_msg>Create log dir if needed<commit_after>package conf

import (
	"os"
	"path"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/Sirupsen/logrus"
	beego "github.com/astaxie/beego/logs"
)

// GOPATH contains the gopath of the env
var GOPATH string

// LogDir is the log directory for logs.json
var LogDir string

// LogFile name
var LogFile string

func init() {
	GOPATH = os.Getenv("GOPATH")
	if GOPATH == "" {
		GOPATH = defaultGOPATH()
	}

	appPath := GOPATH + "/src/github.com/snagles/docker-registry-manager/app"
	LogDir = appPath + "/logs/"
	LogFile = LogDir + "/log.json"

	// create log dir if needed
	if _, err := os.Stat(LogDir); os.IsNotExist(err) {
		if err = os.Mkdir(LogDir, 0755); err != nil {
			logrus.Fatal(err)
		}
	}

	// Create the log file if needed
	if _, err := os.Stat(LogFile); os.IsNotExist(err) {
		if _, err = os.Create(LogFile); err != nil {
			logrus.Fatal(err)
		}
	}

	// Setup logrus
	f, _ := os.OpenFile(LogFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
	logrus.SetOutput(f)
	logrus.SetFormatter(&logrus.JSONFormatter{})
	logrus.AddHook(ContextHook{})

	// Setup Beego logging by registering the hook
	beego.Register("docker-registry-manager", NewBeegoHook)
	beego.SetLogger("docker-registry-manager", "")
	beego.EnableFuncCallDepth(true)
}

type registryLogger struct {
	Level int `json:"level"`
}

func NewBeegoHook() beego.Logger { return &registryLogger{Level: 0} }

// Init returns nothing since theres no additional config optios needed
func (rl *registryLogger) Init(jsonconfig string) error { return nil }

// Destroy is a empty method
func (rl *registryLogger) Destroy() {}

// Flush is a empty method
func (rl *registryLogger) Flush() {}

// WriteMsg will write the msg and level into es
func (rl *registryLogger) WriteMsg(when time.Time, msg string, level int) error {
	if level < int(logrus.GetLevel()) {
		return nil
	}

	switch level {
	// beego is reverse order
	case 0:
		logrus.Panic(msg)
	case 1:
		logrus.Panic(msg)
	case 2:
		logrus.Panic(msg)
	case 3:
		logrus.Error(msg)
	case 4:
		logrus.Warn(msg)
	case 5:
		logrus.Info(msg)
	case 6:
		logrus.Info(msg)
	case 7:
		logrus.Debug(msg)
	}
	return nil
}

type ContextHook struct{}

func (hook ContextHook) Levels() []logrus.Level {
	return logrus.AllLevels
}

func (hook ContextHook) Fire(entry *logrus.Entry) error {
	pc := make([]uintptr, 3, 3)
	cnt := runtime.Callers(6, pc)

	for i := 0; i < cnt; i++ {
		fu := runtime.FuncForPC(pc[i] - 1)
		name := fu.Name()
		if !strings.Contains(name, "github.com/Sirupsen/logrus") {

			if strings.Contains(name, "registryLogger") {
				// Remove the prefix beego attaches
				r := regexp.MustCompile(`\[[A-Z]\] \[(.*):(.*)\] (.*)`)
				message := r.FindAllStringSubmatch(entry.Message, 1)

				// add the caller as a separate field
				entry.Data["file"] = message[0][1]
				line, _ := strconv.Atoi(message[0][2])
				entry.Data["line"] = line
				entry.Message = message[0][3]
				entry.Data["source"] = "beego"
				break
			} else {
				file, line := fu.FileLine(pc[i] - 1)
				entry.Data["file"] = path.Base(file)
				entry.Data["line"] = line
				entry.Data["source"] = "app"
				break
			}
		}

	}
	return nil
}

func defaultGOPATH() string {
	env := "HOME"
	if runtime.GOOS == "windows" {
		env = "USERPROFILE"
	} else if runtime.GOOS == "plan9" {
		env = "home"
	}
	if home := os.Getenv(env); home != "" {
		def := filepath.Join(home, "go")
		if filepath.Clean(def) == filepath.Clean(runtime.GOROOT()) {
			// Don't set the default GOPATH to GOROOT,
			// as that will trigger warnings from the go tool.
			return ""
		}
		return def
	}
	return ""
}
<|endoftext|>


<commit_before>package spec

import (
	"fmt"
	"reflect"

	"golang.org/x/net/http2"
)

const (
	ExpectedConnectionClosed = "Connection closed"
	ExpectedStreamClosed     = "Stream closed"
	ExpectedGoAwayFrame      = "GOAWAY Frame (Error Code: %s)"
	ExpectedRSTStreamFrame   = "RST_STREAM Frame (Error Code: %s)"
)

// VerifyConnectionClose verifies whether the connection was closed.
func VerifyConnectionClose(conn *Conn) error {
	var actual Event

	passed := false
	for !conn.Closed {
		event := conn.WaitEvent()

		switch ev := event.(type) {
		case ConnectionClosedEvent:
			passed = true
		case TimeoutEvent:
			if actual == nil {
				actual = ev
			}
		default:
			actual = ev
		}

		if passed {
			break
		}
	}

	if !passed {
		return &TestError{
			Expected: []string{ExpectedConnectionClosed},
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifyConnectionError verifies whether a connection error of HTTP/2
// has occurred.
func VerifyConnectionError(conn *Conn, codes ...http2.ErrCode) error {
	var actual Event

	passed := false
	for !conn.Closed {
		ev := conn.WaitEvent()

		switch event := ev.(type) {
		case ConnectionClosedEvent:
			passed = true
		case GoAwayFrameEvent:
			passed = VerifyErrorCode(codes, event.ErrCode)
		case TimeoutEvent:
			if actual == nil {
				actual = event
			}
		default:
			actual = event
		}

		if passed {
			break
		}
	}

	if !passed {
		expected := []string{}
		for _, code := range codes {
			expected = append(expected, fmt.Sprintf(ExpectedGoAwayFrame, code))
		}
		expected = append(expected, ExpectedConnectionClosed)

		return &TestError{
			Expected: expected,
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifyStreamError verifies whether a stream error of HTTP/2
// has occurred.
func VerifyStreamError(conn *Conn, codes ...http2.ErrCode) error {
	var actual Event

	passed := false
	for !conn.Closed {
		ev := conn.WaitEvent()

		switch event := ev.(type) {
		case ConnectionClosedEvent:
			passed = true
		case GoAwayFrameEvent:
			passed = VerifyErrorCode(codes, event.ErrCode)
		case RSTStreamFrameEvent:
			passed = VerifyErrorCode(codes, event.ErrCode)
		case TimeoutEvent:
			if actual == nil {
				actual = event
			}
		default:
			actual = event
		}

		if passed {
			break
		}
	}

	if !passed {
		expected := []string{}
		for _, code := range codes {
			expected = append(expected, fmt.Sprintf(ExpectedGoAwayFrame, code))
			expected = append(expected, fmt.Sprintf(ExpectedRSTStreamFrame, code))
		}
		expected = append(expected, ExpectedConnectionClosed)

		return &TestError{
			Expected: expected,
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifyStreamClose verifies whether a stream close of HTTP/2
// has occurred.
func VerifyStreamClose(conn *Conn) error {
	var actual Event

	passed := false
	for !conn.Closed {
		ev := conn.WaitEvent()

		switch event := ev.(type) {
		case DataFrameEvent:
			if event.StreamEnded() {
				passed = true
			}
		case HeadersFrameEvent:
			if event.StreamEnded() {
				passed = true
			}
		case TimeoutEvent:
			if actual == nil {
				actual = event
			}
		default:
			actual = event
		}

		if passed {
			break
		}
	}

	if !passed {
		return &TestError{
			Expected: []string{ExpectedStreamClosed},
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifyHeadersFrame verifies whether a HEADERS frame with specified
// stream ID has received.
func VerifyHeadersFrame(conn *Conn, streamID uint32) error {
	actual, passed := conn.WaitEventByType(EventHeadersFrame)
	switch event := actual.(type) {
	case HeadersFrameEvent:
		passed = (event.Header().StreamID == streamID)
	default:
		passed = false
	}

	if !passed {
		expected := []string{
			fmt.Sprintf("HEADERS Frame (stream_id:%d)", streamID),
		}

		return &TestError{
			Expected: expected,
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifySettingsFrameWithAck verifies whether a SETTINGS frame with
// ACK flag has received.
func VerifySettingsFrameWithAck(conn *Conn) error {
	actual, passed := conn.WaitEventByType(EventSettingsFrame)
	switch event := actual.(type) {
	case SettingsFrameEvent:
		passed = event.IsAck()
	default:
		passed = false
	}

	if !passed {
		expected := []string{
			"SETTINGS Frame (length:0, flags:0x01, stream_id:0)",
		}

		return &TestError{
			Expected: expected,
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifyPingFrameWithAck verifies whether a PING frame with ACK flag
// has received.
func VerifyPingFrameWithAck(conn *Conn, data [8]byte) error {
	actual, passed := conn.WaitEventByType(EventPingFrame)
	switch event := actual.(type) {
	case PingFrameEvent:
		passed = event.IsAck() && reflect.DeepEqual(event.Data, data)
	default:
		passed = false
	}

	if !passed {
		var actualStr string

		expected := []string{
			fmt.Sprintf("PING Frame (length:8, flags:0x01, stream_id:0, opaque_data:%s)", data),
		}

		f, ok := actual.(PingFrameEvent)
		if ok {
			header := f.Header()
			actualStr = fmt.Sprintf(
				"PING Frame ((length:%d, flags:0x%02x, stream_id:%d, opaque_data: %s)",
				header.Type,
				header.Length,
				header.Flags,
				header.StreamID,
				f.Data,
			)
		} else {
			actualStr = actual.String()
		}

		return &TestError{
			Expected: expected,
			Actual:   actualStr,
		}
	}

	return nil
}

// VerifyEventType verifies whether a frame with specified type
// has received.
func VerifyEventType(conn *Conn, et EventType) error {
	var actual Event

	actual, passed := conn.WaitEventByType(et)

	if !passed {
		return &TestError{
			Expected: []string{et.String()},
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifyErrorCode verifies whether the specified error code is
// the expected error code.
func VerifyErrorCode(codes []http2.ErrCode, code http2.ErrCode) bool {
	for _, c := range codes {
		if c == code {
			return true
		}
	}
	return false
}
<commit_msg>Treat RST_STREAM with NO_ERROR as stream close<commit_after>package spec

import (
	"fmt"
	"reflect"

	"golang.org/x/net/http2"
)

const (
	ExpectedConnectionClosed = "Connection closed"
	ExpectedStreamClosed     = "Stream closed"
	ExpectedGoAwayFrame      = "GOAWAY Frame (Error Code: %s)"
	ExpectedRSTStreamFrame   = "RST_STREAM Frame (Error Code: %s)"
)

// VerifyConnectionClose verifies whether the connection was closed.
func VerifyConnectionClose(conn *Conn) error {
	var actual Event

	passed := false
	for !conn.Closed {
		event := conn.WaitEvent()

		switch ev := event.(type) {
		case ConnectionClosedEvent:
			passed = true
		case TimeoutEvent:
			if actual == nil {
				actual = ev
			}
		default:
			actual = ev
		}

		if passed {
			break
		}
	}

	if !passed {
		return &TestError{
			Expected: []string{ExpectedConnectionClosed},
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifyConnectionError verifies whether a connection error of HTTP/2
// has occurred.
func VerifyConnectionError(conn *Conn, codes ...http2.ErrCode) error {
	var actual Event

	passed := false
	for !conn.Closed {
		ev := conn.WaitEvent()

		switch event := ev.(type) {
		case ConnectionClosedEvent:
			passed = true
		case GoAwayFrameEvent:
			passed = VerifyErrorCode(codes, event.ErrCode)
		case TimeoutEvent:
			if actual == nil {
				actual = event
			}
		default:
			actual = event
		}

		if passed {
			break
		}
	}

	if !passed {
		expected := []string{}
		for _, code := range codes {
			expected = append(expected, fmt.Sprintf(ExpectedGoAwayFrame, code))
		}
		expected = append(expected, ExpectedConnectionClosed)

		return &TestError{
			Expected: expected,
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifyStreamError verifies whether a stream error of HTTP/2
// has occurred.
func VerifyStreamError(conn *Conn, codes ...http2.ErrCode) error {
	var actual Event

	passed := false
	for !conn.Closed {
		ev := conn.WaitEvent()

		switch event := ev.(type) {
		case ConnectionClosedEvent:
			passed = true
		case GoAwayFrameEvent:
			passed = VerifyErrorCode(codes, event.ErrCode)
		case RSTStreamFrameEvent:
			passed = VerifyErrorCode(codes, event.ErrCode)
		case TimeoutEvent:
			if actual == nil {
				actual = event
			}
		default:
			actual = event
		}

		if passed {
			break
		}
	}

	if !passed {
		expected := []string{}
		for _, code := range codes {
			expected = append(expected, fmt.Sprintf(ExpectedGoAwayFrame, code))
			expected = append(expected, fmt.Sprintf(ExpectedRSTStreamFrame, code))
		}
		expected = append(expected, ExpectedConnectionClosed)

		return &TestError{
			Expected: expected,
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifyStreamClose verifies whether a stream close of HTTP/2
// has occurred.
func VerifyStreamClose(conn *Conn) error {
	var actual Event

	passed := false
	for !conn.Closed {
		ev := conn.WaitEvent()

		switch event := ev.(type) {
		case DataFrameEvent:
			if event.StreamEnded() {
				passed = true
			}
		case HeadersFrameEvent:
			if event.StreamEnded() {
				passed = true
			}
		case RSTStreamFrameEvent:
			if event.ErrCode == http2.ErrCodeNo {
				passed = true
			}
		case TimeoutEvent:
			if actual == nil {
				actual = event
			}
		default:
			actual = event
		}

		if passed {
			break
		}
	}

	if !passed {
		return &TestError{
			Expected: []string{ExpectedStreamClosed},
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifyHeadersFrame verifies whether a HEADERS frame with specified
// stream ID has received.
func VerifyHeadersFrame(conn *Conn, streamID uint32) error {
	actual, passed := conn.WaitEventByType(EventHeadersFrame)
	switch event := actual.(type) {
	case HeadersFrameEvent:
		passed = (event.Header().StreamID == streamID)
	default:
		passed = false
	}

	if !passed {
		expected := []string{
			fmt.Sprintf("HEADERS Frame (stream_id:%d)", streamID),
		}

		return &TestError{
			Expected: expected,
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifySettingsFrameWithAck verifies whether a SETTINGS frame with
// ACK flag has received.
func VerifySettingsFrameWithAck(conn *Conn) error {
	actual, passed := conn.WaitEventByType(EventSettingsFrame)
	switch event := actual.(type) {
	case SettingsFrameEvent:
		passed = event.IsAck()
	default:
		passed = false
	}

	if !passed {
		expected := []string{
			"SETTINGS Frame (length:0, flags:0x01, stream_id:0)",
		}

		return &TestError{
			Expected: expected,
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifyPingFrameWithAck verifies whether a PING frame with ACK flag
// has received.
func VerifyPingFrameWithAck(conn *Conn, data [8]byte) error {
	actual, passed := conn.WaitEventByType(EventPingFrame)
	switch event := actual.(type) {
	case PingFrameEvent:
		passed = event.IsAck() && reflect.DeepEqual(event.Data, data)
	default:
		passed = false
	}

	if !passed {
		var actualStr string

		expected := []string{
			fmt.Sprintf("PING Frame (length:8, flags:0x01, stream_id:0, opaque_data:%s)", data),
		}

		f, ok := actual.(PingFrameEvent)
		if ok {
			header := f.Header()
			actualStr = fmt.Sprintf(
				"PING Frame ((length:%d, flags:0x%02x, stream_id:%d, opaque_data: %s)",
				header.Type,
				header.Length,
				header.Flags,
				header.StreamID,
				f.Data,
			)
		} else {
			actualStr = actual.String()
		}

		return &TestError{
			Expected: expected,
			Actual:   actualStr,
		}
	}

	return nil
}

// VerifyEventType verifies whether a frame with specified type
// has received.
func VerifyEventType(conn *Conn, et EventType) error {
	var actual Event

	actual, passed := conn.WaitEventByType(et)

	if !passed {
		return &TestError{
			Expected: []string{et.String()},
			Actual:   actual.String(),
		}
	}

	return nil
}

// VerifyErrorCode verifies whether the specified error code is
// the expected error code.
func VerifyErrorCode(codes []http2.ErrCode, code http2.ErrCode) bool {
	for _, c := range codes {
		if c == code {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package packa

import "fmt"

type temporaryError string

func (*temporaryError) Error() string {
	return "packa: temporary error"
}

func (*temporaryError) temporary() bool {
	return true
}

type basicError struct {
	value int
}

func (e *basicError) Error() string {
	return fmt.Sprintf("packa: value %v", e.value)
}

// IsTemporary returns true if err is temporary.
func IsTemporary(err error) bool {
	type temporary interface {
		temporary() bool
	}
	te, ok := err.(temporary)
	return ok && te.Temporary()
}
<commit_msg>errors/custom: add Error comments<commit_after>package packa

import "fmt"

type temporaryError string

// Error implements error interface.
func (*temporaryError) Error() string {
	return "packa: temporary error"
}

func (*temporaryError) temporary() bool {
	return true
}

type basicError struct {
	value int
}

// Error implements error interface.
func (e *basicError) Error() string {
	return fmt.Sprintf("packa: value %v", e.value)
}

// IsTemporary returns true if err is temporary.
func IsTemporary(err error) bool {
	type temporary interface {
		temporary() bool
	}
	te, ok := err.(temporary)
	return ok && te.Temporary()
}
<|endoftext|>


<commit_before>package search

import (
	"net/url"
	"strconv"
)

type search struct {
	Keyword    string
	Pagenation int
	Sort       string
}

func New(keyword string, sort string) *search {
	return &search{
		Keyword:    keyword,
		Pagenation: 0,
		Sort:       sort,
	}
}

func (s *search) GetURL() string {
	q := url.Values{}
	q.Set("pagenetion", strconv.Itoa(s.Pagenation))
	q.Set("q", s.Keyword)
	q.Set("sort", s.Sort)
	u := url.URL{
		Scheme:   "https",
		Host:     "qiita.com",
		Path:     "search",
		RawQuery: q.Encode(),
	}

	return u.String()
}

func (s *search) NextPage() {
	s.Pagenation++
}
<commit_msg>Add search exec method<commit_after>package search

import (
	"net/url"
	"strconv"

	"github.com/PuerkitoBio/goquery"
	"github.com/wasanx25/sreq/config"
)

type search struct {
	Keyword    string
	Pagenation int
	Sort       string
	Contents   []*Content
}

// Content is structure that scraping content from Qiita
type Content struct {
	ID    string
	Title string
	Desc  string
}

func New(keyword string, sort string) *search {
	return &search{
		Keyword:    keyword,
		Pagenation: 0,
		Sort:       sort,
	}
}

func (s *search) GetURL() string {
	q := url.Values{}
	q.Set("pagenetion", strconv.Itoa(s.Pagenation))
	q.Set("q", s.Keyword)
	q.Set("sort", s.Sort)
	u := url.URL{
		Scheme:   "https",
		Host:     "qiita.com",
		Path:     "search",
		RawQuery: q.Encode(),
	}

	return u.String()
}

func (s *search) NextPage() {
	s.Pagenation++
}

func (s *search) Exec() ([]*Content, error) {
	url := config.GetPageURL(s.Keyword, s.Sort, strconv.Itoa(s.Pagenation))
	doc, err := goquery.NewDocument(url)
	if err != nil {
		return nil, err
	}
	doc.Find(".searchResult").Each(s.getAttr)

	return s.Contents, nil
}

func (s *search) getAttr(_ int, q *goquery.Selection) {
	itemID, _ := q.Attr("data-uuid")
	title := q.Find(".searchResult_itemTitle a").Text()
	desc := q.Find(".searchResult_snippet").Text()

	content := &Content{
		ID:    itemID,
		Title: title,
		Desc:  desc,
	}

	s.Contents = append(s.Contents, content)
}
<|endoftext|>


<commit_before>/*--------------------------------------------------------*\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: https://hprose.com                     |
|                                                          |
| rpc/plugins/reverse/provider.go                          |
|                                                          |
| LastModified: May 19, 2021                               |
| Author: Ma Bingyao <andot@hprose.com>                    |
|                                                          |
\*________________________________________________________*/

package reverse

import (
	"context"
	"reflect"
	"sync"
	"sync/atomic"
	"time"

	"github.com/hprose/hprose-golang/v3/io"
	"github.com/hprose/hprose-golang/v3/rpc/core"
)

type contextMissingMethod = func(ctx context.Context, name string, args []interface{}) (result []interface{}, err error)
type missingMethod = func(name string, args []interface{}) (result []interface{}, err error)

type ProviderContext struct {
	core.Context
	client *core.Client
	method core.Method
}

func NewProviderContext(client *core.Client, method core.Method) *ProviderContext {
	return &ProviderContext{
		client: client,
		method: method,
	}
}

func (c *ProviderContext) Client() *core.Client {
	return c.client
}

func (c *ProviderContext) Method() core.Method {
	return c.method
}

func (c *ProviderContext) Clone() core.Context {
	return &ProviderContext{
		c.Context.Clone(),
		c.client,
		c.method,
	}
}

// GetProviderContext returns the *reverse.ProviderContext bound to the context.
func GetProviderContext(ctx context.Context) *ProviderContext {
	if c, ok := core.FromContext(ctx); ok {
		return c.(*ProviderContext)
	}
	return nil
}

type Provider struct {
	client        *core.Client
	proxy         provider
	invokeManager core.PluginManager
	methodManager core.MethodManager
	closed        int32
	RetryInterval time.Duration
	OnError       func(error)
	Debug         bool
}

type provider struct {
	close func() error                      `name:"!!"`
	begin func() ([]call, error)            `name:"!"`
	end   func(results []returnValue) error `name:"="`
}

func NewProvider(client *core.Client, id ...string) *Provider {
	p := &Provider{
		client:        client,
		RetryInterval: time.Second,
		closed:        1,
	}
	if len(id) > 0 && id[0] != "" {
		p.SetID(id[0])
	}
	p.client.UseService(&p.proxy)
	p.invokeManager = core.NewInvokeManager(p.Execute)
	p.methodManager = core.NewMethodManager()
	p.AddFunction(p.methodManager.Names, "~")
	return p
}

func (p *Provider) onError(err error) {
	if p.OnError != nil {
		p.OnError(err)
	}
}

func (p *Provider) Client() *core.Client {
	return p.client
}

func (p *Provider) ID() (id string) {
	if id = p.client.RequestHeaders().GetString("id"); id == "" {
		panic("client unique id not found")
	}
	return
}

func (p *Provider) SetID(id string) {
	p.client.RequestHeaders().Set("id", id)
}

func (p *Provider) Execute(ctx context.Context, name string, args []interface{}) (result []interface{}, err error) {
	method := GetProviderContext(ctx).method
	if method.Missing() {
		if method.PassContext() {
			return method.(interface{}).(contextMissingMethod)(ctx, name, args)
		}
		return method.(interface{}).(missingMethod)(name, args)
	}
	n := len(args)
	var in []reflect.Value
	if method.PassContext() {
		in = make([]reflect.Value, n+1)
		in[0] = reflect.ValueOf(ctx)
		for i := 0; i < n; i++ {
			in[i+1] = reflect.ValueOf(args[i])
		}
	} else {
		in = make([]reflect.Value, n)
		for i := 0; i < n; i++ {
			in[i] = reflect.ValueOf(args[i])
		}
	}
	f := method.Func()
	out := f.Call(in)
	n = len(out)
	if method.ReturnError() {
		if !out[n-1].IsNil() {
			err = out[n-1].Interface().(error)
		}
		out = out[:n-1]
		n--
	}
	for i := 0; i < n; i++ {
		result = append(result, out[i].Interface())
	}
	return
}

func (p *Provider) process(c call) (rv returnValue) {
	index, name, args := c.Value()
	defer func() {
		if e := recover(); e != nil {
			err := core.NewPanicError(e)
			if p.Debug {
				rv = newReturnValue(index, nil, err.String())
			} else {
				rv = newReturnValue(index, nil, err.Error())
			}
		}
	}()
	method := p.Get(name)
	if method == nil {
		return newReturnValue(index, nil, "Can't find this method "+name+"().")
	}
	if !method.Missing() {
		count := len(args)
		parameters := method.Parameters()
		paramTypes := make([]reflect.Type, count)
		if method.Func().Type().IsVariadic() {
			n := len(parameters)
			copy(paramTypes, parameters[:n-1])
			for i := n - 1; i < count; i++ {
				paramTypes[i] = parameters[n-1].Elem()
			}
		} else {
			copy(paramTypes, parameters)
		}
		for i, t := range paramTypes {
			if arg, err := io.Convert(args[i], t); err != nil {
				return newReturnValue(index, nil, err.Error())
			} else {
				args[i] = arg
			}
		}
	}
	ctx := core.WithContext(context.Background(), NewProviderContext(p.client, method))
	results, err := p.invokeManager.Handler().(core.NextInvokeHandler)(ctx, name, args)
	var result interface{}
	switch len(results) {
	case 0:
		result = nil
	case 1:
		result = results[0]
	default:
		result = results
	}
	if err != nil {
		return newReturnValue(index, result, err.Error())
	}
	return newReturnValue(index, result, "")
}

func (p *Provider) dispatch(calls []call) {
	n := len(calls)
	results := make([]returnValue, n)
	var wg sync.WaitGroup
	wg.Add(n)
	for i := 0; i < n; i++ {
		go func(i int) {
			results[i] = p.process(calls[i])
			wg.Done()
		}(i)
	}
	wg.Wait()
	for {
		if err := p.proxy.end(results); err != nil {
			if !core.IsTimeoutError(err) {
				if p.RetryInterval != 0 {
					<-time.After(p.RetryInterval)
				}
				p.onError(err)
			}
			continue
		}
		return
	}
}

func (p *Provider) Listen() {
	if !atomic.CompareAndSwapInt32(&p.closed, 1, 0) {
		return
	}
	for atomic.LoadInt32(&p.closed) == 0 {
		calls, err := p.proxy.begin()
		if err != nil {
			if !core.IsTimeoutError(err) {
				if p.RetryInterval != 0 {
					<-time.After(p.RetryInterval)
				}
				p.onError(err)
			}
			continue
		}
		if calls == nil {
			return
		}
		go p.dispatch(calls)
	}
	atomic.StoreInt32(&p.closed, 1)
}

func (p *Provider) Close() error {
	if atomic.CompareAndSwapInt32(&p.closed, 0, 1) {
		return p.proxy.close()
	}
	return core.ErrClosed
}

// Use plugin handlers.
func (p *Provider) Use(handler ...core.PluginHandler) *Provider {
	invokeHandlers, _ := core.SeparatePluginHandlers(handler)
	if len(invokeHandlers) > 0 {
		p.invokeManager.Use(invokeHandlers...)
	}
	return p
}

// Unuse plugin handlers.
func (p *Provider) Unuse(handler ...core.PluginHandler) *Provider {
	invokeHandlers, _ := core.SeparatePluginHandlers(handler)
	if len(invokeHandlers) > 0 {
		p.invokeManager.Unuse(invokeHandlers...)
	}
	return p
}

// Get returns the published method by name.
func (p *Provider) Get(name string) core.Method {
	return p.methodManager.Get(name)
}

// Remove is used for unpublishing method by the specified name.
func (p *Provider) Remove(name string) *Provider {
	p.methodManager.Remove(name)
	return p
}

// Add is used for publishing the method.
func (p *Provider) Add(method core.Method) *Provider {
	p.methodManager.Add(method)
	return p
}

// AddFunction is used for publishing function f with alias.
func (p *Provider) AddFunction(f interface{}, alias ...string) *Provider {
	p.methodManager.AddFunction(f, alias...)
	return p
}

// AddMethod is used for publishing method named name on target with alias.
func (p *Provider) AddMethod(name string, target interface{}, alias ...string) *Provider {
	p.methodManager.AddMethod(name, target, alias...)
	return p
}

// AddMethods is used for publishing methods named names on target with namespace.
func (p *Provider) AddMethods(names []string, target interface{}, namespace ...string) *Provider {
	p.methodManager.AddMethods(names, target, namespace...)
	return p
}

// AddInstanceMethods is used for publishing all the public methods and func fields with namespace.
func (p *Provider) AddInstanceMethods(target interface{}, namespace ...string) *Provider {
	p.methodManager.AddInstanceMethods(target, namespace...)
	return p
}

// AddAllMethods will publish all methods and non-nil function fields on the
// obj self and on its anonymous or non-anonymous struct fields (or pointer to
// pointer ... to pointer struct fields). This is a recursive operation.
// So it's a pit, if you do not know what you are doing, do not step on.
func (p *Provider) AddAllMethods(target interface{}, namespace ...string) *Provider {
	p.methodManager.AddAllMethods(target, namespace...)
	return p
}

// AddMissingMethod is used for publishing a method,
// all methods not explicitly published will be redirected to this method.
func (p *Provider) AddMissingMethod(f interface{}) *Provider {
	p.methodManager.AddMissingMethod(f)
	return p
}

// AddNetRPCMethods is used for publishing methods defined for net/rpc.
func (p *Provider) AddNetRPCMethods(rcvr interface{}, namespace ...string) *Provider {
	p.methodManager.AddNetRPCMethods(rcvr, namespace...)
	return p
}
<commit_msg>Update provider.go<commit_after>/*--------------------------------------------------------*\
|                                                          |
|                          hprose                          |
|                                                          |
| Official WebSite: https://hprose.com                     |
|                                                          |
| rpc/plugins/reverse/provider.go                          |
|                                                          |
| LastModified: May 19, 2021                               |
| Author: Ma Bingyao <andot@hprose.com>                    |
|                                                          |
\*________________________________________________________*/

package reverse

import (
	"context"
	"reflect"
	"sync"
	"sync/atomic"
	"time"

	"github.com/hprose/hprose-golang/v3/io"
	"github.com/hprose/hprose-golang/v3/rpc/core"
)

type contextMissingMethod = func(ctx context.Context, name string, args []interface{}) (result []interface{}, err error)
type missingMethod = func(name string, args []interface{}) (result []interface{}, err error)

type ProviderContext struct {
	core.Context
	client *core.Client
	method core.Method
}

func NewProviderContext(client *core.Client, method core.Method) *ProviderContext {
	return &ProviderContext{
		client: client,
		method: method,
	}
}

func (c *ProviderContext) Client() *core.Client {
	return c.client
}

func (c *ProviderContext) Method() core.Method {
	return c.method
}

func (c *ProviderContext) Clone() core.Context {
	return &ProviderContext{
		c.Context.Clone(),
		c.client,
		c.method,
	}
}

// GetProviderContext returns the *reverse.ProviderContext bound to the context.
func GetProviderContext(ctx context.Context) *ProviderContext {
	if c, ok := core.FromContext(ctx); ok {
		return c.(*ProviderContext)
	}
	return nil
}

type Provider struct {
	client        *core.Client
	proxy         provider
	invokeManager core.PluginManager
	methodManager core.MethodManager
	closed        int32
	RetryInterval time.Duration
	OnError       func(error)
	Debug         bool
}

type provider struct {
	close func() error                      `name:"!!"`
	begin func() ([]call, error)            `name:"!"`
	end   func(results []returnValue) error `name:"="`
}

func NewProvider(client *core.Client, id ...string) *Provider {
	p := &Provider{
		client:        client,
		RetryInterval: time.Second,
		closed:        1,
	}
	if len(id) > 0 && id[0] != "" {
		p.SetID(id[0])
	}
	p.client.UseService(&p.proxy)
	p.invokeManager = core.NewInvokeManager(p.Execute)
	p.methodManager = core.NewMethodManager()
	p.AddFunction(p.methodManager.Names, "~")
	return p
}

func (p *Provider) onError(err error) {
	if p.OnError != nil {
		p.OnError(err)
	}
}

func (p *Provider) Client() *core.Client {
	return p.client
}

func (p *Provider) ID() (id string) {
	if id = p.client.RequestHeaders().GetString("id"); id == "" {
		panic("client unique id not found")
	}
	return
}

func (p *Provider) SetID(id string) {
	p.client.RequestHeaders().Set("id", id)
}

func (p *Provider) Execute(ctx context.Context, name string, args []interface{}) (result []interface{}, err error) {
	method := GetProviderContext(ctx).method
	if method.Missing() {
		if method.PassContext() {
			return method.(interface{}).(contextMissingMethod)(ctx, name, args)
		}
		return method.(interface{}).(missingMethod)(name, args)
	}
	n := len(args)
	var in []reflect.Value
	if method.PassContext() {
		in = make([]reflect.Value, n+1)
		in[0] = reflect.ValueOf(ctx)
		for i := 0; i < n; i++ {
			in[i+1] = reflect.ValueOf(args[i])
		}
	} else {
		in = make([]reflect.Value, n)
		for i := 0; i < n; i++ {
			in[i] = reflect.ValueOf(args[i])
		}
	}
	f := method.Func()
	out := f.Call(in)
	n = len(out)
	if method.ReturnError() {
		if !out[n-1].IsNil() {
			err = out[n-1].Interface().(error)
		}
		out = out[:n-1]
		n--
	}
	for i := 0; i < n; i++ {
		result = append(result, out[i].Interface())
	}
	return
}

func (p *Provider) process(c call) (rv returnValue) {
	index, name, args := c.Value()
	defer func() {
		if e := recover(); e != nil {
			err := core.NewPanicError(e)
			if p.Debug {
				rv = newReturnValue(index, nil, err.String())
			} else {
				rv = newReturnValue(index, nil, err.Error())
			}
		}
	}()
	method := p.Get(name)
	if method == nil {
		return newReturnValue(index, nil, "Can't find this method "+name+"().")
	}
	if !method.Missing() {
		count := len(args)
		parameters := method.Parameters()
		paramTypes := make([]reflect.Type, count)
		if method.Func().Type().IsVariadic() {
			n := len(parameters)
			copy(paramTypes, parameters[:n-1])
			for i := n - 1; i < count; i++ {
				paramTypes[i] = parameters[n-1].Elem()
			}
		} else {
			copy(paramTypes, parameters)
		}
		for i, t := range paramTypes {
			if arg, err := io.Convert(args[i], t); err != nil {
				return newReturnValue(index, nil, err.Error())
			} else {
				args[i] = arg
			}
		}
	}
	ctx := core.WithContext(context.Background(), NewProviderContext(p.client, method))
	results, err := p.invokeManager.Handler().(core.NextInvokeHandler)(ctx, name, args)
	var result interface{}
	switch len(results) {
	case 0:
		result = nil
	case 1:
		result = results[0]
	default:
		result = results
	}
	if err != nil {
		return newReturnValue(index, result, err.Error())
	}
	return newReturnValue(index, result, "")
}

func (p *Provider) dispatch(calls []call) {
	n := len(calls)
	results := make([]returnValue, n)
	var wg sync.WaitGroup
	wg.Add(n)
	for i := 0; i < n; i++ {
		go func(i int) {
			results[i] = p.process(calls[i])
			wg.Done()
		}(i)
	}
	wg.Wait()
	for {
		if err := p.proxy.end(results); err != nil {
			if !core.IsTimeoutError(err) {
				if p.RetryInterval != 0 {
					<-time.After(p.RetryInterval)
				}
				p.onError(err)
			}
			continue
		}
		return
	}
}

func (p *Provider) Listen() {
	if !atomic.CompareAndSwapInt32(&p.closed, 1, 0) {
		return
	}
	for atomic.LoadInt32(&p.closed) == 0 {
		calls, err := p.proxy.begin()
		if err != nil {
			if !core.IsTimeoutError(err) {
				if p.RetryInterval != 0 {
					<-time.After(p.RetryInterval)
				}
				p.onError(err)
			}
			continue
		}
		if calls == nil {
			return
		}
		go p.dispatch(calls)
	}
}

func (p *Provider) Close() error {
	if atomic.CompareAndSwapInt32(&p.closed, 0, 1) {
		return p.proxy.close()
	}
	return core.ErrClosed
}

// Use plugin handlers.
func (p *Provider) Use(handler ...core.PluginHandler) *Provider {
	invokeHandlers, _ := core.SeparatePluginHandlers(handler)
	if len(invokeHandlers) > 0 {
		p.invokeManager.Use(invokeHandlers...)
	}
	return p
}

// Unuse plugin handlers.
func (p *Provider) Unuse(handler ...core.PluginHandler) *Provider {
	invokeHandlers, _ := core.SeparatePluginHandlers(handler)
	if len(invokeHandlers) > 0 {
		p.invokeManager.Unuse(invokeHandlers...)
	}
	return p
}

// Get returns the published method by name.
func (p *Provider) Get(name string) core.Method {
	return p.methodManager.Get(name)
}

// Remove is used for unpublishing method by the specified name.
func (p *Provider) Remove(name string) *Provider {
	p.methodManager.Remove(name)
	return p
}

// Add is used for publishing the method.
func (p *Provider) Add(method core.Method) *Provider {
	p.methodManager.Add(method)
	return p
}

// AddFunction is used for publishing function f with alias.
func (p *Provider) AddFunction(f interface{}, alias ...string) *Provider {
	p.methodManager.AddFunction(f, alias...)
	return p
}

// AddMethod is used for publishing method named name on target with alias.
func (p *Provider) AddMethod(name string, target interface{}, alias ...string) *Provider {
	p.methodManager.AddMethod(name, target, alias...)
	return p
}

// AddMethods is used for publishing methods named names on target with namespace.
func (p *Provider) AddMethods(names []string, target interface{}, namespace ...string) *Provider {
	p.methodManager.AddMethods(names, target, namespace...)
	return p
}

// AddInstanceMethods is used for publishing all the public methods and func fields with namespace.
func (p *Provider) AddInstanceMethods(target interface{}, namespace ...string) *Provider {
	p.methodManager.AddInstanceMethods(target, namespace...)
	return p
}

// AddAllMethods will publish all methods and non-nil function fields on the
// obj self and on its anonymous or non-anonymous struct fields (or pointer to
// pointer ... to pointer struct fields). This is a recursive operation.
// So it's a pit, if you do not know what you are doing, do not step on.
func (p *Provider) AddAllMethods(target interface{}, namespace ...string) *Provider {
	p.methodManager.AddAllMethods(target, namespace...)
	return p
}

// AddMissingMethod is used for publishing a method,
// all methods not explicitly published will be redirected to this method.
func (p *Provider) AddMissingMethod(f interface{}) *Provider {
	p.methodManager.AddMissingMethod(f)
	return p
}

// AddNetRPCMethods is used for publishing methods defined for net/rpc.
func (p *Provider) AddNetRPCMethods(rcvr interface{}, namespace ...string) *Provider {
	p.methodManager.AddNetRPCMethods(rcvr, namespace...)
	return p
}
<|endoftext|>


<commit_before>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

// Git remote helper for the Keybase file system.

package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"runtime"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/kbfs/env"
	"github.com/keybase/kbfs/kbfsgit"
	"github.com/keybase/kbfs/libfs"
	"github.com/keybase/kbfs/libgit"
	"github.com/keybase/kbfs/libkbfs"
	"github.com/pkg/errors"
)

var version = flag.Bool("version", false, "Print version")

const usageFormatStr = `Usage:
  git-remote-keybase -version

To run against remote KBFS servers:
  git-remote-keybase %s <remote> [keybase://<repo>]

To run in a local testing environment:
  git-remote-keybase %s <remote> [keybase://<repo>]

Defaults:
%s
`

func getUsageString(ctx libkbfs.Context) string {
	remoteUsageStr := libkbfs.GetRemoteUsageString()
	localUsageStr := libkbfs.GetLocalUsageString()
	defaultUsageStr := libkbfs.GetDefaultsUsageString(ctx)
	return fmt.Sprintf(
		usageFormatStr, remoteUsageStr, localUsageStr, defaultUsageStr)
}

func getLocalGitDir() (gitDir string, err error) {
	gitDir = os.Getenv("GIT_DIR")
	fi, err := os.Stat(gitDir)
	if err != nil {
		return "", err
	}
	if !fi.IsDir() {
		return "", errors.Errorf("GIT_DIR=%s, but is not a dir", gitDir)
	}
	// On Windows, git annoyingly puts normal slashes in the
	// environment variable.
	return filepath.FromSlash(gitDir), nil
}

func checkService(kbCtx env.Context) *libfs.Error {
	// Trying to dial the service seems like the best
	// platform-agnostic way of seeing if the service is up.  Stat-ing
	// the socket file, for example, doesn't work for Windows named
	// pipes.
	s, err := libkb.NewSocket(kbCtx.GetGlobalContext())
	if err != nil {
		return libfs.InitError(err.Error())
	}
	c, err := s.DialSocket()
	if err != nil {
		if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
			return libfs.InitError(
				"Keybase isn't running. Open the Keybase app.")
		}
		return libfs.InitError(
			"Keybase isn't running. Try `run_keybase`.")
	}
	err = c.Close()
	if err != nil {
		return libfs.InitError(err.Error())
	}
	return nil
}

func start() (startErr *libfs.Error) {
	kbCtx := env.NewContext()

	switch kbCtx.GetRunMode() {
	case libkb.ProductionRunMode:
	case libkb.StagingRunMode:
		fmt.Fprintf(os.Stderr, "Running in staging mode\n")
	case libkb.DevelRunMode:
		fmt.Fprintf(os.Stderr, "Running in devel mode\n")
	default:
		panic(fmt.Sprintf("Unexpected run mode: %s", kbCtx.GetRunMode()))
	}

	defaultParams, storageRoot, err := libgit.Params(kbCtx,
		kbCtx.GetDataDir(), nil)
	if err != nil {
		return libfs.InitError(err.Error())
	}
	defer func() {
		rmErr := os.RemoveAll(storageRoot)
		if rmErr != nil {
			fmt.Fprintf(os.Stderr,
				"Error cleaning storage dir %s: %+v\n", storageRoot, rmErr)
		}
	}()
	defaultLogPath := filepath.Join(kbCtx.GetLogDir(), libkb.GitLogFileName)

	// Make sure the service is running before blocking on a connection to it.
	startErr = checkService(kbCtx)
	if startErr != nil {
		return startErr
	}

	// Duplicate the stderr fd, so that when the logger closes it when
	// redirecting log messages to a file, we will still be able to
	// write status updates back to the git process.
	stderrFile, err := dupStderr()
	if err != nil {
		return libfs.InitError(err.Error())
	}
	defer stderrFile.Close()

	defer func() {
		// Now that the stderr has been duplicated, print all errors
		// to the duplicate as well as to the new `os.Stderr` down in
		// `main()`, so that the error shows up both in the log and to
		// the user.
		if startErr != nil {
			fmt.Fprintf(stderrFile, "git-remote-keybase error: (%d) %s\n",
				startErr.Code, startErr.Message)
		}
	}()

	kbfsParams := libkbfs.AddFlagsWithDefaults(
		flag.CommandLine, defaultParams, defaultLogPath)
	flag.Parse()

	if *version {
		fmt.Printf("%s\n", libkbfs.VersionString())
		return nil
	}

	if len(flag.Args()) < 1 {
		fmt.Print(getUsageString(kbCtx))
		return libfs.InitError("no remote repo specified")
	}

	remote := flag.Arg(0)
	var repo string
	if len(flag.Args()) > 1 {
		repo = flag.Arg(1)
	}

	if len(flag.Args()) > 2 {
		fmt.Print(getUsageString(kbCtx))
		return libfs.InitError("extra arguments specified (flags go before the first argument)")
	}

	gitDir, err := getLocalGitDir()
	if err != nil {
		return libfs.InitError(err.Error())
	}

	options := kbfsgit.StartOptions{
		KbfsParams: *kbfsParams,
		Remote:     remote,
		Repo:       repo,
		GitDir:     gitDir,
	}

	ctx := context.Background()
	return kbfsgit.Start(
		ctx, options, kbCtx, defaultLogPath, os.Stdin, os.Stdout, stderrFile)
}

func main() {
	runMode := os.Getenv("KEYBASE_RUN_MODE")
	if len(runMode) == 0 {
		// Default to prod.
		os.Setenv("KEYBASE_RUN_MODE", "prod")
	}

	err := start()
	if err != nil {
		fmt.Fprintf(os.Stderr, "git-remote-keybase error: (%d) %s\n",
			err.Code, err.Message)
		os.Exit(err.Code)
	}
	os.Exit(0)
}
<commit_msg>git-remote-keybase: don't check the git dir before taking commands<commit_after>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

// Git remote helper for the Keybase file system.

package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"runtime"

	"github.com/keybase/client/go/libkb"
	"github.com/keybase/kbfs/env"
	"github.com/keybase/kbfs/kbfsgit"
	"github.com/keybase/kbfs/libfs"
	"github.com/keybase/kbfs/libgit"
	"github.com/keybase/kbfs/libkbfs"
)

var version = flag.Bool("version", false, "Print version")

const usageFormatStr = `Usage:
  git-remote-keybase -version

To run against remote KBFS servers:
  git-remote-keybase %s <remote> [keybase://<repo>]

To run in a local testing environment:
  git-remote-keybase %s <remote> [keybase://<repo>]

Defaults:
%s
`

func getUsageString(ctx libkbfs.Context) string {
	remoteUsageStr := libkbfs.GetRemoteUsageString()
	localUsageStr := libkbfs.GetLocalUsageString()
	defaultUsageStr := libkbfs.GetDefaultsUsageString(ctx)
	return fmt.Sprintf(
		usageFormatStr, remoteUsageStr, localUsageStr, defaultUsageStr)
}

func getLocalGitDir() (gitDir string) {
	gitDir = os.Getenv("GIT_DIR")
	// On Windows, git annoyingly puts normal slashes in the
	// environment variable.
	return filepath.FromSlash(gitDir)
}

func checkService(kbCtx env.Context) *libfs.Error {
	// Trying to dial the service seems like the best
	// platform-agnostic way of seeing if the service is up.  Stat-ing
	// the socket file, for example, doesn't work for Windows named
	// pipes.
	s, err := libkb.NewSocket(kbCtx.GetGlobalContext())
	if err != nil {
		return libfs.InitError(err.Error())
	}
	c, err := s.DialSocket()
	if err != nil {
		if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
			return libfs.InitError(
				"Keybase isn't running. Open the Keybase app.")
		}
		return libfs.InitError(
			"Keybase isn't running. Try `run_keybase`.")
	}
	err = c.Close()
	if err != nil {
		return libfs.InitError(err.Error())
	}
	return nil
}

func start() (startErr *libfs.Error) {
	kbCtx := env.NewContext()

	switch kbCtx.GetRunMode() {
	case libkb.ProductionRunMode:
	case libkb.StagingRunMode:
		fmt.Fprintf(os.Stderr, "Running in staging mode\n")
	case libkb.DevelRunMode:
		fmt.Fprintf(os.Stderr, "Running in devel mode\n")
	default:
		panic(fmt.Sprintf("Unexpected run mode: %s", kbCtx.GetRunMode()))
	}

	defaultParams, storageRoot, err := libgit.Params(kbCtx,
		kbCtx.GetDataDir(), nil)
	if err != nil {
		return libfs.InitError(err.Error())
	}
	defer func() {
		rmErr := os.RemoveAll(storageRoot)
		if rmErr != nil {
			fmt.Fprintf(os.Stderr,
				"Error cleaning storage dir %s: %+v\n", storageRoot, rmErr)
		}
	}()
	defaultLogPath := filepath.Join(kbCtx.GetLogDir(), libkb.GitLogFileName)

	// Make sure the service is running before blocking on a connection to it.
	startErr = checkService(kbCtx)
	if startErr != nil {
		return startErr
	}

	// Duplicate the stderr fd, so that when the logger closes it when
	// redirecting log messages to a file, we will still be able to
	// write status updates back to the git process.
	stderrFile, err := dupStderr()
	if err != nil {
		return libfs.InitError(err.Error())
	}
	defer stderrFile.Close()

	defer func() {
		// Now that the stderr has been duplicated, print all errors
		// to the duplicate as well as to the new `os.Stderr` down in
		// `main()`, so that the error shows up both in the log and to
		// the user.
		if startErr != nil {
			fmt.Fprintf(stderrFile, "git-remote-keybase error: (%d) %s\n",
				startErr.Code, startErr.Message)
		}
	}()

	kbfsParams := libkbfs.AddFlagsWithDefaults(
		flag.CommandLine, defaultParams, defaultLogPath)
	flag.Parse()

	if *version {
		fmt.Printf("%s\n", libkbfs.VersionString())
		return nil
	}

	if len(flag.Args()) < 1 {
		fmt.Print(getUsageString(kbCtx))
		return libfs.InitError("no remote repo specified")
	}

	remote := flag.Arg(0)
	var repo string
	if len(flag.Args()) > 1 {
		repo = flag.Arg(1)
	}

	if len(flag.Args()) > 2 {
		fmt.Print(getUsageString(kbCtx))
		return libfs.InitError("extra arguments specified (flags go before the first argument)")
	}

	options := kbfsgit.StartOptions{
		KbfsParams: *kbfsParams,
		Remote:     remote,
		Repo:       repo,
		GitDir:     getLocalGitDir(),
	}

	ctx := context.Background()
	return kbfsgit.Start(
		ctx, options, kbCtx, defaultLogPath, os.Stdin, os.Stdout, stderrFile)
}

func main() {
	runMode := os.Getenv("KEYBASE_RUN_MODE")
	if len(runMode) == 0 {
		// Default to prod.
		os.Setenv("KEYBASE_RUN_MODE", "prod")
	}

	err := start()
	if err != nil {
		fmt.Fprintf(os.Stderr, "git-remote-keybase error: (%d) %s\n",
			err.Code, err.Message)
		os.Exit(err.Code)
	}
	os.Exit(0)
}
<|endoftext|>