<commit_before>/*
 * Copyright 2020 The Knative 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 trigger

import (
	"context"
	"fmt"

	"knative.dev/eventing-kafka-broker/control-plane/pkg/contract"
	"knative.dev/eventing-kafka-broker/control-plane/pkg/reconciler/kafka"

	"go.uber.org/zap"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/util/retry"
	eventing "knative.dev/eventing/pkg/apis/eventing/v1"
	eventingclientset "knative.dev/eventing/pkg/client/clientset/versioned"
	eventinglisters "knative.dev/eventing/pkg/client/listers/eventing/v1"
	"knative.dev/pkg/controller"
	"knative.dev/pkg/reconciler"
	"knative.dev/pkg/resolver"

	"knative.dev/eventing-kafka-broker/control-plane/pkg/config"
	coreconfig "knative.dev/eventing-kafka-broker/control-plane/pkg/core/config"
	"knative.dev/eventing-kafka-broker/control-plane/pkg/log"
	"knative.dev/eventing-kafka-broker/control-plane/pkg/reconciler/base"
)

type Reconciler struct {
	*base.Reconciler

	BrokerLister   eventinglisters.BrokerLister
	EventingClient eventingclientset.Interface
	Resolver       *resolver.URIResolver

	Configs *config.Env
}

func (r *Reconciler) ReconcileKind(ctx context.Context, trigger *eventing.Trigger) reconciler.Event {
	return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
		return r.reconcileKind(ctx, trigger)
	})
}

func (r *Reconciler) FinalizeKind(ctx context.Context, trigger *eventing.Trigger) reconciler.Event {
	return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
		return r.finalizeKind(ctx, trigger)
	})
}

func (r *Reconciler) finalizeKind(ctx context.Context, trigger *eventing.Trigger) reconciler.Event {

	logger := log.Logger(ctx, "finalize", trigger)

	broker, err := r.BrokerLister.Brokers(trigger.Namespace).Get(trigger.Spec.Broker)
	if err != nil && !apierrors.IsNotFound(err) {
		return fmt.Errorf("failed to get broker from lister: %w", err)
	}

	if apierrors.IsNotFound(err) {
		// If the broker is deleted, resources associated with the Trigger will be deleted.
		return nil
	}

	// Get data plane config map.
	dataPlaneConfigMap, err := r.GetOrCreateDataPlaneConfigMap(ctx)
	if err != nil {
		return fmt.Errorf("failed to get data plane config map %s: %w", r.Configs.DataPlaneConfigMapAsString(), err)
	}

	logger.Debug("Got data plane config map")

	// Get contract data.
	ct, err := r.GetDataPlaneConfigMapData(logger, dataPlaneConfigMap)
	if err != nil {
		return fmt.Errorf("failed to get contract: %w", err)
	}

	logger.Debug(
		"Got contract data from data plane config map",
		zap.Any(base.ContractLogKey, (*log.ContractMarshaller)(ct)),
	)

	brokerIndex := coreconfig.FindResource(ct, broker.UID)
	if brokerIndex == coreconfig.NoResource {
		// If the broker is not there, resources associated with the Trigger are deleted accordingly.
		return nil
	}

	logger.Debug("Found Broker", zap.Int("brokerIndex", brokerIndex))

	egresses := ct.Resources[brokerIndex].Egresses
	triggerIndex := coreconfig.FindEgress(egresses, trigger.UID)
	if triggerIndex == coreconfig.NoEgress {
		// The trigger is not there, resources associated with the Trigger are deleted accordingly.
		logger.Debug("trigger not found in config map")

		return nil
	}

	logger.Debug("Found Trigger", zap.Int("triggerIndex", brokerIndex))

	// Delete the Trigger from the config map data.
	ct.Resources[brokerIndex].Egresses = deleteTrigger(egresses, triggerIndex)

	// Increment volume generation
	coreconfig.IncrementContractGeneration(ct)

	// Update data plane config map.
	err = r.UpdateDataPlaneConfigMap(ctx, ct, dataPlaneConfigMap)
	if err != nil {
		return err
	}

	logger.Debug("Updated data plane config map", zap.String("configmap", r.Configs.DataPlaneConfigMapAsString()))

	// Update volume generation annotation of dispatcher pods
	if err := r.UpdateDispatcherPodsAnnotation(ctx, logger, ct.Generation); err != nil {
		// Failing to update dispatcher pods annotation leads to config map refresh delayed by several seconds.
		// The delete trigger will eventually be seen by the data plane pods, so log out the error and move on to the
		// next step.
		logger.Warn(
			"Failed to update dispatcher pod annotation to trigger an immediate config map refresh",
			zap.Error(err),
		)
	} else {
		logger.Debug("Updated dispatcher pod annotation successfully")
	}

	return nil
}

func (r *Reconciler) getTriggerConfig(ctx context.Context, trigger *eventing.Trigger) (*contract.Egress, error) {
	destination, err := r.Resolver.URIFromDestinationV1(ctx, trigger.Spec.Subscriber, trigger)
	if err != nil {
		return nil, fmt.Errorf("failed to resolve Trigger.Spec.Subscriber: %w", err)
	}
	trigger.Status.SubscriberURI = destination

	egress := &contract.Egress{
		Destination:   destination.String(),
		ConsumerGroup: string(trigger.UID),
		Uid:           string(trigger.UID),
	}

	if trigger.Spec.Filter != nil && trigger.Spec.Filter.Attributes != nil {
		egress.Filter = &contract.Filter{
			Attributes: trigger.Spec.Filter.Attributes,
		}
	}

	egressConfig, err := coreconfig.EgressConfigFromDelivery(ctx, r.Resolver, trigger, trigger.Spec.Delivery, r.Configs.DefaultBackoffDelayMs)
	if err != nil {
		return nil, err
	}
	egress.EgressConfig = egressConfig

	return egress, nil
}

func deleteTrigger(egresses []*contract.Egress, index int) []*contract.Egress {
	if len(egresses) == 1 {
		return nil
	}

	// replace the trigger to be deleted with the last one.
	egresses[index] = egresses[len(egresses)-1]
	// truncate the array.
	return egresses[:len(egresses)-1]
}

func (r *Reconciler) reconcileKind(ctx context.Context, trigger *eventing.Trigger) reconciler.Event {

	logger := log.Logger(ctx, "reconcile", trigger)

	statusConditionManager := statusConditionManager{
		Trigger:  trigger,
		Configs:  r.Configs,
		Recorder: controller.GetEventRecorder(ctx),
	}

	broker, err := r.BrokerLister.Brokers(trigger.Namespace).Get(trigger.Spec.Broker)
	if err != nil && !apierrors.IsNotFound(err) {
		return statusConditionManager.failedToGetBroker(err)
	}

	if apierrors.IsNotFound(err) {

		// Actually check if the broker doesn't exist.
		// Note: do not introduce another `broker` variable with `:`
		broker, err = r.EventingClient.EventingV1().Brokers(trigger.Namespace).Get(ctx, trigger.Spec.Broker, metav1.GetOptions{})

		if apierrors.IsNotFound(err) {

			logger.Debug("broker not found", zap.String("finalizeDuringReconcile", "notFound"))
			// The associated broker doesn't exist anymore, so clean up Trigger resources.
			return r.FinalizeKind(ctx, trigger)
		}
	}

	// Ignore Triggers that are associated with a Broker we don't own.
	if isOur, brokerClass := isOurBroker(broker); !isOur {
		logger.Debug("Ignoring Trigger", zap.String(eventing.BrokerClassAnnotationKey, brokerClass))
		return nil
	}

	if !broker.GetDeletionTimestamp().IsZero() {

		logger.Debug("broker deleted", zap.String("finalizeDuringReconcile", "deleted"))

		// The associated broker doesn't exist anymore, so clean up Trigger resources.
		return r.FinalizeKind(ctx, trigger)
	}

	statusConditionManager.propagateBrokerCondition(broker)

	// Get data plane config map.
	contractConfigMap, err := r.GetOrCreateDataPlaneConfigMap(ctx)
	if err != nil {
		return statusConditionManager.failedToGetDataPlaneConfigMap(err)
	}

	logger.Debug("Got contract config map")

	// Get data plane config data.
	ct, err := r.GetDataPlaneConfigMapData(logger, contractConfigMap)
	if err != nil || ct == nil {
		return statusConditionManager.failedToGetDataPlaneConfigFromConfigMap(err)
	}

	logger.Debug(
		"Got contract data from config map",
		zap.Any(base.ContractLogKey, (*log.ContractMarshaller)(ct)),
	)

	brokerIndex := coreconfig.FindResource(ct, broker.UID)
	if brokerIndex == coreconfig.NoResource {
		return statusConditionManager.brokerNotFoundInDataPlaneConfigMap()
	}
	triggerIndex := coreconfig.FindEgress(ct.Resources[brokerIndex].Egresses, trigger.UID)

	triggerConfig, err := r.getTriggerConfig(ctx, trigger)
	if err != nil {
		return statusConditionManager.failedToResolveTriggerConfig(err)
	}
	statusConditionManager.subscriberResolved()

	changed := coreconfig.AddOrUpdateEgressConfig(ct, brokerIndex, triggerConfig, triggerIndex)

	coreconfig.IncrementContractGeneration(ct)

	logger.Debug("Egress changes", zap.Int("changed", changed))

	if changed == coreconfig.EgressChanged {
		// Update the configuration map with the new dataPlaneConfig data.
		if err := r.UpdateDataPlaneConfigMap(ctx, ct, contractConfigMap); err != nil {
			trigger.Status.MarkDependencyFailed(string(base.ConditionConfigMapUpdated), err.Error())
			return err
		}

		// Update volume generation annotation of dispatcher pods
		if err := r.UpdateDispatcherPodsAnnotation(ctx, logger, ct.Generation); err != nil {
			// Failing to update dispatcher pods annotation leads to config map refresh delayed by several seconds.
			// Since the dispatcher side is the consumer side, we don't lose availability, and we can consider the Trigger
			// ready. So, log out the error and move on to the next step.
			logger.Warn(
				"Failed to update dispatcher pod annotation to trigger an immediate config map refresh",
				zap.Error(err),
			)

			statusConditionManager.failedToUpdateDispatcherPodsAnnotation(err)
		} else {
			logger.Debug("Updated dispatcher pod annotation")
		}
	}

	logger.Debug("Contract config map updated")

	return statusConditionManager.reconciled()
}

func isOurBroker(broker *eventing.Broker) (bool, string) {
	brokerClass := broker.GetAnnotations()[eventing.BrokerClassAnnotationKey]
	return brokerClass == kafka.BrokerClass, brokerClass
}
<commit_msg>Move reconcileKind method (#669)<commit_after>/*
 * Copyright 2020 The Knative 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 trigger

import (
	"context"
	"fmt"

	"knative.dev/eventing-kafka-broker/control-plane/pkg/contract"
	"knative.dev/eventing-kafka-broker/control-plane/pkg/reconciler/kafka"

	"go.uber.org/zap"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/util/retry"
	eventing "knative.dev/eventing/pkg/apis/eventing/v1"
	eventingclientset "knative.dev/eventing/pkg/client/clientset/versioned"
	eventinglisters "knative.dev/eventing/pkg/client/listers/eventing/v1"
	"knative.dev/pkg/controller"
	"knative.dev/pkg/reconciler"
	"knative.dev/pkg/resolver"

	"knative.dev/eventing-kafka-broker/control-plane/pkg/config"
	coreconfig "knative.dev/eventing-kafka-broker/control-plane/pkg/core/config"
	"knative.dev/eventing-kafka-broker/control-plane/pkg/log"
	"knative.dev/eventing-kafka-broker/control-plane/pkg/reconciler/base"
)

type Reconciler struct {
	*base.Reconciler

	BrokerLister   eventinglisters.BrokerLister
	EventingClient eventingclientset.Interface
	Resolver       *resolver.URIResolver

	Configs *config.Env
}

func (r *Reconciler) ReconcileKind(ctx context.Context, trigger *eventing.Trigger) reconciler.Event {
	return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
		return r.reconcileKind(ctx, trigger)
	})
}

func (r *Reconciler) reconcileKind(ctx context.Context, trigger *eventing.Trigger) reconciler.Event {

	logger := log.Logger(ctx, "reconcile", trigger)

	statusConditionManager := statusConditionManager{
		Trigger:  trigger,
		Configs:  r.Configs,
		Recorder: controller.GetEventRecorder(ctx),
	}

	broker, err := r.BrokerLister.Brokers(trigger.Namespace).Get(trigger.Spec.Broker)
	if err != nil && !apierrors.IsNotFound(err) {
		return statusConditionManager.failedToGetBroker(err)
	}

	if apierrors.IsNotFound(err) {

		// Actually check if the broker doesn't exist.
		// Note: do not introduce another `broker` variable with `:`
		broker, err = r.EventingClient.EventingV1().Brokers(trigger.Namespace).Get(ctx, trigger.Spec.Broker, metav1.GetOptions{})

		if apierrors.IsNotFound(err) {

			logger.Debug("broker not found", zap.String("finalizeDuringReconcile", "notFound"))
			// The associated broker doesn't exist anymore, so clean up Trigger resources.
			return r.FinalizeKind(ctx, trigger)
		}
	}

	// Ignore Triggers that are associated with a Broker we don't own.
	if isOur, brokerClass := isOurBroker(broker); !isOur {
		logger.Debug("Ignoring Trigger", zap.String(eventing.BrokerClassAnnotationKey, brokerClass))
		return nil
	}

	if !broker.GetDeletionTimestamp().IsZero() {

		logger.Debug("broker deleted", zap.String("finalizeDuringReconcile", "deleted"))

		// The associated broker doesn't exist anymore, so clean up Trigger resources.
		return r.FinalizeKind(ctx, trigger)
	}

	statusConditionManager.propagateBrokerCondition(broker)

	// Get data plane config map.
	contractConfigMap, err := r.GetOrCreateDataPlaneConfigMap(ctx)
	if err != nil {
		return statusConditionManager.failedToGetDataPlaneConfigMap(err)
	}

	logger.Debug("Got contract config map")

	// Get data plane config data.
	ct, err := r.GetDataPlaneConfigMapData(logger, contractConfigMap)
	if err != nil || ct == nil {
		return statusConditionManager.failedToGetDataPlaneConfigFromConfigMap(err)
	}

	logger.Debug(
		"Got contract data from config map",
		zap.Any(base.ContractLogKey, (*log.ContractMarshaller)(ct)),
	)

	brokerIndex := coreconfig.FindResource(ct, broker.UID)
	if brokerIndex == coreconfig.NoResource {
		return statusConditionManager.brokerNotFoundInDataPlaneConfigMap()
	}
	triggerIndex := coreconfig.FindEgress(ct.Resources[brokerIndex].Egresses, trigger.UID)

	triggerConfig, err := r.getTriggerConfig(ctx, trigger)
	if err != nil {
		return statusConditionManager.failedToResolveTriggerConfig(err)
	}
	statusConditionManager.subscriberResolved()

	changed := coreconfig.AddOrUpdateEgressConfig(ct, brokerIndex, triggerConfig, triggerIndex)

	coreconfig.IncrementContractGeneration(ct)

	logger.Debug("Egress changes", zap.Int("changed", changed))

	if changed == coreconfig.EgressChanged {
		// Update the configuration map with the new dataPlaneConfig data.
		if err := r.UpdateDataPlaneConfigMap(ctx, ct, contractConfigMap); err != nil {
			trigger.Status.MarkDependencyFailed(string(base.ConditionConfigMapUpdated), err.Error())
			return err
		}

		// Update volume generation annotation of dispatcher pods
		if err := r.UpdateDispatcherPodsAnnotation(ctx, logger, ct.Generation); err != nil {
			// Failing to update dispatcher pods annotation leads to config map refresh delayed by several seconds.
			// Since the dispatcher side is the consumer side, we don't lose availability, and we can consider the Trigger
			// ready. So, log out the error and move on to the next step.
			logger.Warn(
				"Failed to update dispatcher pod annotation to trigger an immediate config map refresh",
				zap.Error(err),
			)

			statusConditionManager.failedToUpdateDispatcherPodsAnnotation(err)
		} else {
			logger.Debug("Updated dispatcher pod annotation")
		}
	}

	logger.Debug("Contract config map updated")

	return statusConditionManager.reconciled()
}

func (r *Reconciler) FinalizeKind(ctx context.Context, trigger *eventing.Trigger) reconciler.Event {
	return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
		return r.finalizeKind(ctx, trigger)
	})
}

func (r *Reconciler) finalizeKind(ctx context.Context, trigger *eventing.Trigger) reconciler.Event {

	logger := log.Logger(ctx, "finalize", trigger)

	broker, err := r.BrokerLister.Brokers(trigger.Namespace).Get(trigger.Spec.Broker)
	if err != nil && !apierrors.IsNotFound(err) {
		return fmt.Errorf("failed to get broker from lister: %w", err)
	}

	if apierrors.IsNotFound(err) {
		// If the broker is deleted, resources associated with the Trigger will be deleted.
		return nil
	}

	// Get data plane config map.
	dataPlaneConfigMap, err := r.GetOrCreateDataPlaneConfigMap(ctx)
	if err != nil {
		return fmt.Errorf("failed to get data plane config map %s: %w", r.Configs.DataPlaneConfigMapAsString(), err)
	}

	logger.Debug("Got data plane config map")

	// Get contract data.
	ct, err := r.GetDataPlaneConfigMapData(logger, dataPlaneConfigMap)
	if err != nil {
		return fmt.Errorf("failed to get contract: %w", err)
	}

	logger.Debug(
		"Got contract data from data plane config map",
		zap.Any(base.ContractLogKey, (*log.ContractMarshaller)(ct)),
	)

	brokerIndex := coreconfig.FindResource(ct, broker.UID)
	if brokerIndex == coreconfig.NoResource {
		// If the broker is not there, resources associated with the Trigger are deleted accordingly.
		return nil
	}

	logger.Debug("Found Broker", zap.Int("brokerIndex", brokerIndex))

	egresses := ct.Resources[brokerIndex].Egresses
	triggerIndex := coreconfig.FindEgress(egresses, trigger.UID)
	if triggerIndex == coreconfig.NoEgress {
		// The trigger is not there, resources associated with the Trigger are deleted accordingly.
		logger.Debug("trigger not found in config map")

		return nil
	}

	logger.Debug("Found Trigger", zap.Int("triggerIndex", brokerIndex))

	// Delete the Trigger from the config map data.
	ct.Resources[brokerIndex].Egresses = deleteTrigger(egresses, triggerIndex)

	// Increment volume generation
	coreconfig.IncrementContractGeneration(ct)

	// Update data plane config map.
	err = r.UpdateDataPlaneConfigMap(ctx, ct, dataPlaneConfigMap)
	if err != nil {
		return err
	}

	logger.Debug("Updated data plane config map", zap.String("configmap", r.Configs.DataPlaneConfigMapAsString()))

	// Update volume generation annotation of dispatcher pods
	if err := r.UpdateDispatcherPodsAnnotation(ctx, logger, ct.Generation); err != nil {
		// Failing to update dispatcher pods annotation leads to config map refresh delayed by several seconds.
		// The delete trigger will eventually be seen by the data plane pods, so log out the error and move on to the
		// next step.
		logger.Warn(
			"Failed to update dispatcher pod annotation to trigger an immediate config map refresh",
			zap.Error(err),
		)
	} else {
		logger.Debug("Updated dispatcher pod annotation successfully")
	}

	return nil
}

func (r *Reconciler) getTriggerConfig(ctx context.Context, trigger *eventing.Trigger) (*contract.Egress, error) {
	destination, err := r.Resolver.URIFromDestinationV1(ctx, trigger.Spec.Subscriber, trigger)
	if err != nil {
		return nil, fmt.Errorf("failed to resolve Trigger.Spec.Subscriber: %w", err)
	}
	trigger.Status.SubscriberURI = destination

	egress := &contract.Egress{
		Destination:   destination.String(),
		ConsumerGroup: string(trigger.UID),
		Uid:           string(trigger.UID),
	}

	if trigger.Spec.Filter != nil && trigger.Spec.Filter.Attributes != nil {
		egress.Filter = &contract.Filter{
			Attributes: trigger.Spec.Filter.Attributes,
		}
	}

	egressConfig, err := coreconfig.EgressConfigFromDelivery(ctx, r.Resolver, trigger, trigger.Spec.Delivery, r.Configs.DefaultBackoffDelayMs)
	if err != nil {
		return nil, err
	}
	egress.EgressConfig = egressConfig

	return egress, nil
}

func deleteTrigger(egresses []*contract.Egress, index int) []*contract.Egress {
	if len(egresses) == 1 {
		return nil
	}

	// replace the trigger to be deleted with the last one.
	egresses[index] = egresses[len(egresses)-1]
	// truncate the array.
	return egresses[:len(egresses)-1]
}

func isOurBroker(broker *eventing.Broker) (bool, string) {
	brokerClass := broker.GetAnnotations()[eventing.BrokerClassAnnotationKey]
	return brokerClass == kafka.BrokerClass, brokerClass
}
<|endoftext|>


<commit_before>// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package middleware

import (
	"fmt"
	"path"
	"strings"

	"gopkg.in/macaron.v1"

	"github.com/gogits/gogs/models"
	"github.com/gogits/gogs/modules/git"
	"github.com/gogits/gogs/modules/log"
	"github.com/gogits/gogs/modules/setting"
)

func ApiRepoAssignment() macaron.Handler {
	return func(ctx *Context) {
		userName := ctx.Params(":username")
		repoName := ctx.Params(":reponame")

		var (
			owner *models.User
			err   error
		)

		// Check if the user is the same as the repository owner.
		if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
			owner = ctx.User
		} else {
			owner, err = models.GetUserByName(userName)
			if err != nil {
				if models.IsErrUserNotExist(err) {
					ctx.Error(404)
				} else {
					ctx.APIError(500, "GetUserByName", err)
				}
				return
			}
		}
		ctx.Repo.Owner = owner

		// Get repository.
		repo, err := models.GetRepositoryByName(owner.Id, repoName)
		if err != nil {
			if models.IsErrRepoNotExist(err) {
				ctx.Error(404)
			} else {
				ctx.APIError(500, "GetRepositoryByName", err)
			}
			return
		} else if err = repo.GetOwner(); err != nil {
			ctx.APIError(500, "GetOwner", err)
			return
		}

		mode, err := models.AccessLevel(ctx.User, repo)
		if err != nil {
			ctx.APIError(500, "AccessLevel", err)
			return
		}

		ctx.Repo.AccessMode = mode

		// Check access.
		if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE {
			ctx.Error(404)
			return
		}

		ctx.Repo.Repository = repo
	}
}

// RepoRef handles repository reference name including those contain `/`.
func RepoRef() macaron.Handler {
	return func(ctx *Context) {
		// Empty repository does not have reference information.
		if ctx.Repo.Repository.IsBare {
			return
		}

		var (
			refName string
			err     error
		)

		// For API calls.
		if ctx.Repo.GitRepo == nil {
			repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
			gitRepo, err := git.OpenRepository(repoPath)
			if err != nil {
				ctx.Handle(500, "RepoRef Invalid repo "+repoPath, err)
				return
			}
			ctx.Repo.GitRepo = gitRepo
		}

		// Get default branch.
		if len(ctx.Params("*")) == 0 {
			refName = ctx.Repo.Repository.DefaultBranch
			if !ctx.Repo.GitRepo.IsBranchExist(refName) {
				brs, err := ctx.Repo.GitRepo.GetBranches()
				if err != nil {
					ctx.Handle(500, "GetBranches", err)
					return
				}
				refName = brs[0]
			}
			ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfBranch(refName)
			if err != nil {
				ctx.Handle(500, "GetCommitOfBranch", err)
				return
			}
			ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
			ctx.Repo.IsBranch = true

		} else {
			hasMatched := false
			parts := strings.Split(ctx.Params("*"), "/")
			for i, part := range parts {
				refName = strings.TrimPrefix(refName+"/"+part, "/")

				if ctx.Repo.GitRepo.IsBranchExist(refName) ||
					ctx.Repo.GitRepo.IsTagExist(refName) {
					if i < len(parts)-1 {
						ctx.Repo.TreeName = strings.Join(parts[i+1:], "/")
					}
					hasMatched = true
					break
				}
			}
			if !hasMatched && len(parts[0]) == 40 {
				refName = parts[0]
				ctx.Repo.TreeName = strings.Join(parts[1:], "/")
			}

			if ctx.Repo.GitRepo.IsBranchExist(refName) {
				ctx.Repo.IsBranch = true

				ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfBranch(refName)
				if err != nil {
					ctx.Handle(500, "GetCommitOfBranch", err)
					return
				}
				ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()

			} else if ctx.Repo.GitRepo.IsTagExist(refName) {
				ctx.Repo.IsTag = true
				ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfTag(refName)
				if err != nil {
					ctx.Handle(500, "GetCommitOfTag", err)
					return
				}
				ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
			} else if len(refName) == 40 {
				ctx.Repo.IsCommit = true
				ctx.Repo.CommitID = refName

				ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
				if err != nil {
					ctx.Handle(404, "GetCommit", nil)
					return
				}
			} else {
				ctx.Handle(404, "RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
				return
			}
		}

		ctx.Repo.BranchName = refName
		ctx.Data["BranchName"] = ctx.Repo.BranchName
		ctx.Data["CommitID"] = ctx.Repo.CommitID
		ctx.Data["IsBranch"] = ctx.Repo.IsBranch
		ctx.Data["IsTag"] = ctx.Repo.IsTag
		ctx.Data["IsCommit"] = ctx.Repo.IsCommit

		ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount()
		if err != nil {
			ctx.Handle(500, "CommitsCount", err)
			return
		}
		ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
	}
}

func RetrieveBaseRepo(ctx *Context, repo *models.Repository) {
	// Non-fork repository will not return error in this method.
	if err := repo.GetBaseRepo(); err != nil {
		if models.IsErrRepoNotExist(err) {
			repo.IsFork = false
			repo.ForkID = 0
			return
		}
		ctx.Handle(500, "GetBaseRepo", err)
		return
	} else if err = repo.BaseRepo.GetOwner(); err != nil {
		ctx.Handle(500, "BaseRepo.GetOwner", err)
		return
	}

	bsaeRepo := repo.BaseRepo
	baseGitRepo, err := git.OpenRepository(models.RepoPath(bsaeRepo.Owner.Name, bsaeRepo.Name))
	if err != nil {
		ctx.Handle(500, "OpenRepository", err)
		return
	}
	if len(bsaeRepo.DefaultBranch) > 0 && baseGitRepo.IsBranchExist(bsaeRepo.DefaultBranch) {
		ctx.Data["BaseDefaultBranch"] = bsaeRepo.DefaultBranch
	} else {
		baseBranches, err := baseGitRepo.GetBranches()
		if err != nil {
			ctx.Handle(500, "GetBranches", err)
			return
		}
		if len(baseBranches) > 0 {
			ctx.Data["BaseDefaultBranch"] = baseBranches[0]
		}
	}
}

func RepoAssignment(args ...bool) macaron.Handler {
	return func(ctx *Context) {
		ctx.Repo = &RepoContext{}

		var (
			displayBare bool // To display bare page if it is a bare repo.
		)
		if len(args) >= 1 {
			displayBare = args[0]
		}

		var (
			owner *models.User
			err   error
		)

		userName := ctx.Params(":username")
		repoName := ctx.Params(":reponame")
		refName := ctx.Params(":branchname")
		if len(refName) == 0 {
			refName = ctx.Params(":path")
		}

		// Check if the user is the same as the repository owner
		if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
			owner = ctx.User
		} else {
			owner, err = models.GetUserByName(userName)
			if err != nil {
				if models.IsErrUserNotExist(err) {
					ctx.Handle(404, "GetUserByName", err)
				} else {
					ctx.Handle(500, "GetUserByName", err)
				}
				return
			}
		}
		ctx.Repo.Owner = owner

		// Get repository.
		repo, err := models.GetRepositoryByName(owner.Id, repoName)
		if err != nil {
			if models.IsErrRepoNotExist(err) {
				ctx.Handle(404, "GetRepositoryByName", err)
			} else {
				ctx.Handle(500, "GetRepositoryByName", err)
			}
			return
		} else if err = repo.GetOwner(); err != nil {
			ctx.Handle(500, "GetOwner", err)
			return
		}

		// Admin has super access.
		if ctx.IsSigned && ctx.User.IsAdmin {
			ctx.Repo.AccessMode = models.ACCESS_MODE_OWNER
		} else {
			mode, err := models.AccessLevel(ctx.User, repo)
			if err != nil {
				ctx.Handle(500, "AccessLevel", err)
				return
			}
			ctx.Repo.AccessMode = mode
		}

		// Check access.
		if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE {
			ctx.Handle(404, "no access right", err)
			return
		}
		ctx.Data["HasAccess"] = true

		if repo.IsMirror {
			ctx.Repo.Mirror, err = models.GetMirror(repo.ID)
			if err != nil {
				ctx.Handle(500, "GetMirror", err)
				return
			}
			ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval
		}

		ctx.Repo.Repository = repo
		ctx.Data["IsBareRepo"] = ctx.Repo.Repository.IsBare

		gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
		if err != nil {
			ctx.Handle(500, "RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
			return
		}
		ctx.Repo.GitRepo = gitRepo
		ctx.Repo.RepoLink = repo.RepoLink()
		ctx.Data["RepoLink"] = ctx.Repo.RepoLink
		ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name

		tags, err := ctx.Repo.GitRepo.GetTags()
		if err != nil {
			ctx.Handle(500, "GetTags", err)
			return
		}
		ctx.Data["Tags"] = tags
		ctx.Repo.Repository.NumTags = len(tags)

		if repo.IsFork {
			RetrieveBaseRepo(ctx, repo)
			if ctx.Written() {
				return
			}
		}

		ctx.Data["Title"] = owner.Name + "/" + repo.Name
		ctx.Data["Repository"] = repo
		ctx.Data["Owner"] = ctx.Repo.Repository.Owner
		ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
		ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
		ctx.Data["IsRepositoryPusher"] = ctx.Repo.IsPusher()

		ctx.Data["DisableSSH"] = setting.DisableSSH
		ctx.Data["CloneLink"] = repo.CloneLink()
		ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()

		if ctx.IsSigned {
			ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.Id, repo.ID)
			ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.Id, repo.ID)
		}

		// repo is bare and display enable
		if ctx.Repo.Repository.IsBare {
			log.Debug("Bare repository: %s", ctx.Repo.RepoLink)
			// NOTE: to prevent templating error
			ctx.Data["BranchName"] = ""
			if displayBare {
				if !ctx.Repo.IsAdmin() {
					ctx.Flash.Info(ctx.Tr("repo.repo_is_empty"), true)
				}
				ctx.HTML(200, "repo/bare")
			}
			return
		}

		ctx.Data["TagName"] = ctx.Repo.TagName
		brs, err := ctx.Repo.GitRepo.GetBranches()
		if err != nil {
			ctx.Handle(500, "GetBranches", err)
			return
		}
		ctx.Data["Branches"] = brs
		ctx.Data["BrancheCount"] = len(brs)

		// If not branch selected, try default one.
		// If default branch doesn't exists, fall back to some other branch.
		if len(ctx.Repo.BranchName) == 0 {
			if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
				ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
			} else if len(brs) > 0 {
				ctx.Repo.BranchName = brs[0]
			}
		}

		ctx.Data["BranchName"] = ctx.Repo.BranchName
		ctx.Data["CommitID"] = ctx.Repo.CommitID

		if ctx.Query("go-get") == "1" {
			ctx.Data["GoGetImport"] = path.Join(setting.Domain, setting.AppSubUrl, owner.Name, repo.Name)
			prefix := path.Join(setting.AppUrl, owner.Name, repo.Name, "src", ctx.Repo.BranchName)
			ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
			ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
		}
	}
}

func RequireRepoAdmin() macaron.Handler {
	return func(ctx *Context) {
		if !ctx.Repo.IsAdmin() {
			ctx.Handle(404, ctx.Req.RequestURI, nil)
			return
		}
	}
}

func RequireRepoPusher() macaron.Handler {
	return func(ctx *Context) {
		if !ctx.Repo.IsPusher() {
			ctx.Handle(404, ctx.Req.RequestURI, nil)
			return
		}
	}
}

// GitHookService checks if repository Git hooks service has been enabled.
func GitHookService() macaron.Handler {
	return func(ctx *Context) {
		if !ctx.User.CanEditGitHook() {
			ctx.Handle(404, "GitHookService", nil)
			return
		}
	}
}
<commit_msg>fix #2085<commit_after>// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package middleware

import (
	"fmt"
	"path"
	"strings"

	"gopkg.in/macaron.v1"

	"github.com/gogits/gogs/models"
	"github.com/gogits/gogs/modules/git"
	"github.com/gogits/gogs/modules/log"
	"github.com/gogits/gogs/modules/setting"
)

func ApiRepoAssignment() macaron.Handler {
	return func(ctx *Context) {
		ctx.Repo = &RepoContext{}

		userName := ctx.Params(":username")
		repoName := ctx.Params(":reponame")

		var (
			owner *models.User
			err   error
		)

		// Check if the user is the same as the repository owner.
		if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
			owner = ctx.User
		} else {
			owner, err = models.GetUserByName(userName)
			if err != nil {
				if models.IsErrUserNotExist(err) {
					ctx.Error(404)
				} else {
					ctx.APIError(500, "GetUserByName", err)
				}
				return
			}
		}
		ctx.Repo.Owner = owner

		// Get repository.
		repo, err := models.GetRepositoryByName(owner.Id, repoName)
		if err != nil {
			if models.IsErrRepoNotExist(err) {
				ctx.Error(404)
			} else {
				ctx.APIError(500, "GetRepositoryByName", err)
			}
			return
		} else if err = repo.GetOwner(); err != nil {
			ctx.APIError(500, "GetOwner", err)
			return
		}

		mode, err := models.AccessLevel(ctx.User, repo)
		if err != nil {
			ctx.APIError(500, "AccessLevel", err)
			return
		}

		ctx.Repo.AccessMode = mode

		// Check access.
		if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE {
			ctx.Error(404)
			return
		}

		ctx.Repo.Repository = repo
	}
}

// RepoRef handles repository reference name including those contain `/`.
func RepoRef() macaron.Handler {
	return func(ctx *Context) {
		// Empty repository does not have reference information.
		if ctx.Repo.Repository.IsBare {
			return
		}

		var (
			refName string
			err     error
		)

		// For API calls.
		if ctx.Repo.GitRepo == nil {
			repoPath := models.RepoPath(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
			gitRepo, err := git.OpenRepository(repoPath)
			if err != nil {
				ctx.Handle(500, "RepoRef Invalid repo "+repoPath, err)
				return
			}
			ctx.Repo.GitRepo = gitRepo
		}

		// Get default branch.
		if len(ctx.Params("*")) == 0 {
			refName = ctx.Repo.Repository.DefaultBranch
			if !ctx.Repo.GitRepo.IsBranchExist(refName) {
				brs, err := ctx.Repo.GitRepo.GetBranches()
				if err != nil {
					ctx.Handle(500, "GetBranches", err)
					return
				}
				refName = brs[0]
			}
			ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfBranch(refName)
			if err != nil {
				ctx.Handle(500, "GetCommitOfBranch", err)
				return
			}
			ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
			ctx.Repo.IsBranch = true

		} else {
			hasMatched := false
			parts := strings.Split(ctx.Params("*"), "/")
			for i, part := range parts {
				refName = strings.TrimPrefix(refName+"/"+part, "/")

				if ctx.Repo.GitRepo.IsBranchExist(refName) ||
					ctx.Repo.GitRepo.IsTagExist(refName) {
					if i < len(parts)-1 {
						ctx.Repo.TreeName = strings.Join(parts[i+1:], "/")
					}
					hasMatched = true
					break
				}
			}
			if !hasMatched && len(parts[0]) == 40 {
				refName = parts[0]
				ctx.Repo.TreeName = strings.Join(parts[1:], "/")
			}

			if ctx.Repo.GitRepo.IsBranchExist(refName) {
				ctx.Repo.IsBranch = true

				ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfBranch(refName)
				if err != nil {
					ctx.Handle(500, "GetCommitOfBranch", err)
					return
				}
				ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()

			} else if ctx.Repo.GitRepo.IsTagExist(refName) {
				ctx.Repo.IsTag = true
				ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommitOfTag(refName)
				if err != nil {
					ctx.Handle(500, "GetCommitOfTag", err)
					return
				}
				ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
			} else if len(refName) == 40 {
				ctx.Repo.IsCommit = true
				ctx.Repo.CommitID = refName

				ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
				if err != nil {
					ctx.Handle(404, "GetCommit", nil)
					return
				}
			} else {
				ctx.Handle(404, "RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
				return
			}
		}

		ctx.Repo.BranchName = refName
		ctx.Data["BranchName"] = ctx.Repo.BranchName
		ctx.Data["CommitID"] = ctx.Repo.CommitID
		ctx.Data["IsBranch"] = ctx.Repo.IsBranch
		ctx.Data["IsTag"] = ctx.Repo.IsTag
		ctx.Data["IsCommit"] = ctx.Repo.IsCommit

		ctx.Repo.CommitsCount, err = ctx.Repo.Commit.CommitsCount()
		if err != nil {
			ctx.Handle(500, "CommitsCount", err)
			return
		}
		ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
	}
}

func RetrieveBaseRepo(ctx *Context, repo *models.Repository) {
	// Non-fork repository will not return error in this method.
	if err := repo.GetBaseRepo(); err != nil {
		if models.IsErrRepoNotExist(err) {
			repo.IsFork = false
			repo.ForkID = 0
			return
		}
		ctx.Handle(500, "GetBaseRepo", err)
		return
	} else if err = repo.BaseRepo.GetOwner(); err != nil {
		ctx.Handle(500, "BaseRepo.GetOwner", err)
		return
	}

	bsaeRepo := repo.BaseRepo
	baseGitRepo, err := git.OpenRepository(models.RepoPath(bsaeRepo.Owner.Name, bsaeRepo.Name))
	if err != nil {
		ctx.Handle(500, "OpenRepository", err)
		return
	}
	if len(bsaeRepo.DefaultBranch) > 0 && baseGitRepo.IsBranchExist(bsaeRepo.DefaultBranch) {
		ctx.Data["BaseDefaultBranch"] = bsaeRepo.DefaultBranch
	} else {
		baseBranches, err := baseGitRepo.GetBranches()
		if err != nil {
			ctx.Handle(500, "GetBranches", err)
			return
		}
		if len(baseBranches) > 0 {
			ctx.Data["BaseDefaultBranch"] = baseBranches[0]
		}
	}
}

func RepoAssignment(args ...bool) macaron.Handler {
	return func(ctx *Context) {
		ctx.Repo = &RepoContext{}

		var (
			displayBare bool // To display bare page if it is a bare repo.
		)
		if len(args) >= 1 {
			displayBare = args[0]
		}

		var (
			owner *models.User
			err   error
		)

		userName := ctx.Params(":username")
		repoName := ctx.Params(":reponame")
		refName := ctx.Params(":branchname")
		if len(refName) == 0 {
			refName = ctx.Params(":path")
		}

		// Check if the user is the same as the repository owner
		if ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName) {
			owner = ctx.User
		} else {
			owner, err = models.GetUserByName(userName)
			if err != nil {
				if models.IsErrUserNotExist(err) {
					ctx.Handle(404, "GetUserByName", err)
				} else {
					ctx.Handle(500, "GetUserByName", err)
				}
				return
			}
		}
		ctx.Repo.Owner = owner

		// Get repository.
		repo, err := models.GetRepositoryByName(owner.Id, repoName)
		if err != nil {
			if models.IsErrRepoNotExist(err) {
				ctx.Handle(404, "GetRepositoryByName", err)
			} else {
				ctx.Handle(500, "GetRepositoryByName", err)
			}
			return
		} else if err = repo.GetOwner(); err != nil {
			ctx.Handle(500, "GetOwner", err)
			return
		}

		// Admin has super access.
		if ctx.IsSigned && ctx.User.IsAdmin {
			ctx.Repo.AccessMode = models.ACCESS_MODE_OWNER
		} else {
			mode, err := models.AccessLevel(ctx.User, repo)
			if err != nil {
				ctx.Handle(500, "AccessLevel", err)
				return
			}
			ctx.Repo.AccessMode = mode
		}

		// Check access.
		if ctx.Repo.AccessMode == models.ACCESS_MODE_NONE {
			ctx.Handle(404, "no access right", err)
			return
		}
		ctx.Data["HasAccess"] = true

		if repo.IsMirror {
			ctx.Repo.Mirror, err = models.GetMirror(repo.ID)
			if err != nil {
				ctx.Handle(500, "GetMirror", err)
				return
			}
			ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval
		}

		ctx.Repo.Repository = repo
		ctx.Data["IsBareRepo"] = ctx.Repo.Repository.IsBare

		gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
		if err != nil {
			ctx.Handle(500, "RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
			return
		}
		ctx.Repo.GitRepo = gitRepo
		ctx.Repo.RepoLink = repo.RepoLink()
		ctx.Data["RepoLink"] = ctx.Repo.RepoLink
		ctx.Data["RepoRelPath"] = ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name

		tags, err := ctx.Repo.GitRepo.GetTags()
		if err != nil {
			ctx.Handle(500, "GetTags", err)
			return
		}
		ctx.Data["Tags"] = tags
		ctx.Repo.Repository.NumTags = len(tags)

		if repo.IsFork {
			RetrieveBaseRepo(ctx, repo)
			if ctx.Written() {
				return
			}
		}

		ctx.Data["Title"] = owner.Name + "/" + repo.Name
		ctx.Data["Repository"] = repo
		ctx.Data["Owner"] = ctx.Repo.Repository.Owner
		ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner()
		ctx.Data["IsRepositoryAdmin"] = ctx.Repo.IsAdmin()
		ctx.Data["IsRepositoryPusher"] = ctx.Repo.IsPusher()

		ctx.Data["DisableSSH"] = setting.DisableSSH
		ctx.Data["CloneLink"] = repo.CloneLink()
		ctx.Data["WikiCloneLink"] = repo.WikiCloneLink()

		if ctx.IsSigned {
			ctx.Data["IsWatchingRepo"] = models.IsWatching(ctx.User.Id, repo.ID)
			ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.Id, repo.ID)
		}

		// repo is bare and display enable
		if ctx.Repo.Repository.IsBare {
			log.Debug("Bare repository: %s", ctx.Repo.RepoLink)
			// NOTE: to prevent templating error
			ctx.Data["BranchName"] = ""
			if displayBare {
				if !ctx.Repo.IsAdmin() {
					ctx.Flash.Info(ctx.Tr("repo.repo_is_empty"), true)
				}
				ctx.HTML(200, "repo/bare")
			}
			return
		}

		ctx.Data["TagName"] = ctx.Repo.TagName
		brs, err := ctx.Repo.GitRepo.GetBranches()
		if err != nil {
			ctx.Handle(500, "GetBranches", err)
			return
		}
		ctx.Data["Branches"] = brs
		ctx.Data["BrancheCount"] = len(brs)

		// If not branch selected, try default one.
		// If default branch doesn't exists, fall back to some other branch.
		if len(ctx.Repo.BranchName) == 0 {
			if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
				ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
			} else if len(brs) > 0 {
				ctx.Repo.BranchName = brs[0]
			}
		}

		ctx.Data["BranchName"] = ctx.Repo.BranchName
		ctx.Data["CommitID"] = ctx.Repo.CommitID

		if ctx.Query("go-get") == "1" {
			ctx.Data["GoGetImport"] = path.Join(setting.Domain, setting.AppSubUrl, owner.Name, repo.Name)
			prefix := path.Join(setting.AppUrl, owner.Name, repo.Name, "src", ctx.Repo.BranchName)
			ctx.Data["GoDocDirectory"] = prefix + "{/dir}"
			ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
		}
	}
}

func RequireRepoAdmin() macaron.Handler {
	return func(ctx *Context) {
		if !ctx.Repo.IsAdmin() {
			ctx.Handle(404, ctx.Req.RequestURI, nil)
			return
		}
	}
}

func RequireRepoPusher() macaron.Handler {
	return func(ctx *Context) {
		if !ctx.Repo.IsPusher() {
			ctx.Handle(404, ctx.Req.RequestURI, nil)
			return
		}
	}
}

// GitHookService checks if repository Git hooks service has been enabled.
func GitHookService() macaron.Handler {
	return func(ctx *Context) {
		if !ctx.User.CanEditGitHook() {
			ctx.Handle(404, "GitHookService", nil)
			return
		}
	}
}
<|endoftext|>


<commit_before>package renter

import (
	"errors"
	"io"
	"os"
	"sync/atomic"
	"time"

	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/modules/renter/contractor"
)

var (
	errInsufficientHosts  = errors.New("insufficient hosts to recover file")
	errInsufficientPieces = errors.New("couldn't fetch enough pieces to recover data")
)

// A fetcher fetches pieces from a host. This interface exists to facilitate
// easy testing.
type fetcher interface {
	// pieces returns the set of pieces corresponding to a given chunk.
	pieces(chunk uint64) []pieceData

	// fetch returns the data specified by piece metadata.
	fetch(pieceData) ([]byte, error)
}

// A hostFetcher fetches pieces from a host. It implements the fetcher
// interface.
type hostFetcher struct {
	downloader contractor.Downloader
	pieceMap   map[uint64][]pieceData
	masterKey  crypto.TwofishKey
}

// pieces returns the pieces stored on this host that are part of a given
// chunk.
func (hf *hostFetcher) pieces(chunk uint64) []pieceData {
	return hf.pieceMap[chunk]
}

// fetch downloads the piece specified by p.
func (hf *hostFetcher) fetch(p pieceData) ([]byte, error) {
	// request piece
	data, err := hf.downloader.Sector(p.MerkleRoot)
	if err != nil {
		return nil, err
	}

	// generate decryption key
	key := deriveKey(hf.masterKey, p.Chunk, p.Piece)

	// decrypt and return
	return key.DecryptBytes(data)
}

// newHostFetcher creates a new hostFetcher.
func newHostFetcher(d contractor.Downloader, fc fileContract, masterKey crypto.TwofishKey) *hostFetcher {
	// make piece map
	pieceMap := make(map[uint64][]pieceData)
	for _, p := range fc.Pieces {
		pieceMap[p.Chunk] = append(pieceMap[p.Chunk], p)
	}
	return &hostFetcher{
		downloader: d,
		pieceMap:   pieceMap,
		masterKey:  masterKey,
	}
}

// checkHosts checks that a set of hosts is sufficient to download a file.
func checkHosts(hosts []fetcher, minPieces int, numChunks uint64) error {
	for i := uint64(0); i < numChunks; i++ {
		pieces := 0
		for _, h := range hosts {
			pieces += len(h.pieces(i))
		}
		if pieces < minPieces {
			return errInsufficientHosts
		}
	}
	return nil
}

// A download is a file download that has been queued by the renter.
type download struct {
	// NOTE: received is the first field to ensure 64-bit alignment, which is
	// required for atomic operations.
	received uint64

	startTime   time.Time
	siapath     string
	destination string

	erasureCode modules.ErasureCoder
	chunkSize   uint64
	fileSize    uint64
	hosts       []fetcher
}

// getPiece locates and downloads a specific piece.
func (d *download) getPiece(chunkIndex, pieceIndex uint64) []byte {
	for _, h := range d.hosts {
		for _, p := range h.pieces(chunkIndex) {
			if p.Piece == pieceIndex {
				data, err := h.fetch(p)
				if err != nil {
					break // try next host
				}
				return data
			}
		}
	}
	return nil
}

// run performs the actual download. It spawns one worker per host, and
// instructs them to sequentially download chunks. It then writes the
// recovered chunks to w.
func (d *download) run(w io.Writer) error {
	var received uint64
	for i := uint64(0); received < d.fileSize; i++ {
		// load pieces into chunk
		chunk := make([][]byte, d.erasureCode.NumPieces())
		left := d.erasureCode.MinPieces()
		// pick hosts at random
		chunkOrder, err := crypto.Perm(len(chunk))
		if err != nil {
			return err
		}
		for _, j := range chunkOrder {
			chunk[j] = d.getPiece(i, uint64(j))
			if chunk[j] != nil {
				left--
			} else {
			}
			if left == 0 {
				break
			}
		}
		if left != 0 {
			return errInsufficientPieces
		}

		// Write pieces to w. We always write chunkSize bytes unless this is
		// the last chunk; in that case, we write the remainder.
		n := d.chunkSize
		if n > d.fileSize-received {
			n = d.fileSize - received
		}
		err = d.erasureCode.Recover(chunk, uint64(n), w)
		if err != nil {
			return err
		}
		received += n
		atomic.AddUint64(&d.received, n)
	}

	return nil
}

// newDownload initializes and returns a download object.
func (f *file) newDownload(hosts []fetcher, destination string) *download {
	return &download{
		erasureCode: f.erasureCode,
		chunkSize:   f.chunkSize(),
		fileSize:    f.size,
		hosts:       hosts,

		startTime:   time.Now(),
		received:    0,
		siapath:     f.name,
		destination: destination,
	}
}

// Download downloads a file, identified by its path, to the destination
// specified.
func (r *Renter) Download(path, destination string) error {
	// Lookup the file associated with the nickname.
	lockID := r.mu.Lock()
	file, exists := r.files[path]
	r.mu.Unlock(lockID)
	if !exists {
		return errors.New("no file with that path")
	}

	// Copy the file's metadata
	// TODO: this is ugly because we only have the Contracts method for
	// looking up contracts.
	contracts := make(map[*fileContract]contractor.Contract)
	file.mu.RLock()
	for _, c := range r.hostContractor.Contracts() {
		fc, ok := file.contracts[c.ID]
		if ok {
			contracts[&fc] = c
		}
	}
	file.mu.RUnlock()

	// Initiate connections to each host.
	var hosts []fetcher
	for fc, c := range contracts {
		// TODO: connect in parallel
		d, err := r.hostContractor.Downloader(c)
		if err != nil {
			continue
		}
		defer d.Close()
		hosts = append(hosts, newHostFetcher(d, *fc, file.masterKey))
	}

	// Check that this host set is sufficient to download the file.
	err := checkHosts(hosts, file.erasureCode.MinPieces(), file.numChunks())
	if err != nil {
		return err
	}

	// Create file on disk with the correct permissions.
	perm := os.FileMode(file.mode)
	if perm == 0 {
		// sane default
		perm = 0666
	}
	f, err := os.OpenFile(destination, os.O_CREATE|os.O_RDWR|os.O_TRUNC, perm)
	if err != nil {
		return err
	}
	defer f.Close()

	// Create the download object.
	d := file.newDownload(hosts, destination)

	// Add the download to the download queue.
	lockID = r.mu.Lock()
	r.downloadQueue = append(r.downloadQueue, d)
	r.mu.Unlock(lockID)

	// Perform download.
	err = d.run(f)
	if err != nil {
		// File could not be downloaded; delete the copy on disk.
		os.Remove(destination)
		return err
	}

	return nil
}

// DownloadQueue returns the list of downloads in the queue.
func (r *Renter) DownloadQueue() []modules.DownloadInfo {
	lockID := r.mu.RLock()
	defer r.mu.RUnlock(lockID)

	// order from most recent to least recent
	downloads := make([]modules.DownloadInfo, len(r.downloadQueue))
	for i := range r.downloadQueue {
		d := r.downloadQueue[len(r.downloadQueue)-i-1]
		downloads[i] = modules.DownloadInfo{
			SiaPath:     d.siapath,
			Destination: d.destination,
			Filesize:    d.fileSize,
			Received:    atomic.LoadUint64(&d.received),
			StartTime:   d.startTime,
		}
	}
	return downloads
}
<commit_msg>more helpful download error messages<commit_after>package renter

import (
	"errors"
	"fmt"
	"io"
	"os"
	"strings"
	"sync/atomic"
	"time"

	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/modules/renter/contractor"
)

var (
	errInsufficientHosts  = errors.New("insufficient hosts to recover file")
	errInsufficientPieces = errors.New("couldn't fetch enough pieces to recover data")
)

// A fetcher fetches pieces from a host. This interface exists to facilitate
// easy testing.
type fetcher interface {
	// pieces returns the set of pieces corresponding to a given chunk.
	pieces(chunk uint64) []pieceData

	// fetch returns the data specified by piece metadata.
	fetch(pieceData) ([]byte, error)
}

// A hostFetcher fetches pieces from a host. It implements the fetcher
// interface.
type hostFetcher struct {
	downloader contractor.Downloader
	pieceMap   map[uint64][]pieceData
	masterKey  crypto.TwofishKey
}

// pieces returns the pieces stored on this host that are part of a given
// chunk.
func (hf *hostFetcher) pieces(chunk uint64) []pieceData {
	return hf.pieceMap[chunk]
}

// fetch downloads the piece specified by p.
func (hf *hostFetcher) fetch(p pieceData) ([]byte, error) {
	// request piece
	data, err := hf.downloader.Sector(p.MerkleRoot)
	if err != nil {
		return nil, err
	}

	// generate decryption key
	key := deriveKey(hf.masterKey, p.Chunk, p.Piece)

	// decrypt and return
	return key.DecryptBytes(data)
}

// newHostFetcher creates a new hostFetcher.
func newHostFetcher(d contractor.Downloader, fc fileContract, masterKey crypto.TwofishKey) *hostFetcher {
	// make piece map
	pieceMap := make(map[uint64][]pieceData)
	for _, p := range fc.Pieces {
		pieceMap[p.Chunk] = append(pieceMap[p.Chunk], p)
	}
	return &hostFetcher{
		downloader: d,
		pieceMap:   pieceMap,
		masterKey:  masterKey,
	}
}

// checkHosts checks that a set of hosts is sufficient to download a file.
func checkHosts(hosts []fetcher, minPieces int, numChunks uint64) error {
	for i := uint64(0); i < numChunks; i++ {
		pieces := 0
		for _, h := range hosts {
			pieces += len(h.pieces(i))
		}
		if pieces < minPieces {
			return errInsufficientHosts
		}
	}
	return nil
}

// A download is a file download that has been queued by the renter.
type download struct {
	// NOTE: received is the first field to ensure 64-bit alignment, which is
	// required for atomic operations.
	received uint64

	startTime   time.Time
	siapath     string
	destination string

	erasureCode modules.ErasureCoder
	chunkSize   uint64
	fileSize    uint64
	hosts       []fetcher
}

// getPiece locates and downloads a specific piece.
func (d *download) getPiece(chunkIndex, pieceIndex uint64) []byte {
	for _, h := range d.hosts {
		for _, p := range h.pieces(chunkIndex) {
			if p.Piece == pieceIndex {
				data, err := h.fetch(p)
				if err != nil {
					break // try next host
				}
				return data
			}
		}
	}
	return nil
}

// run performs the actual download. It spawns one worker per host, and
// instructs them to sequentially download chunks. It then writes the
// recovered chunks to w.
func (d *download) run(w io.Writer) error {
	var received uint64
	for i := uint64(0); received < d.fileSize; i++ {
		// load pieces into chunk
		chunk := make([][]byte, d.erasureCode.NumPieces())
		left := d.erasureCode.MinPieces()
		// pick hosts at random
		chunkOrder, err := crypto.Perm(len(chunk))
		if err != nil {
			return err
		}
		for _, j := range chunkOrder {
			chunk[j] = d.getPiece(i, uint64(j))
			if chunk[j] != nil {
				left--
			} else {
			}
			if left == 0 {
				break
			}
		}
		if left != 0 {
			return errInsufficientPieces
		}

		// Write pieces to w. We always write chunkSize bytes unless this is
		// the last chunk; in that case, we write the remainder.
		n := d.chunkSize
		if n > d.fileSize-received {
			n = d.fileSize - received
		}
		err = d.erasureCode.Recover(chunk, uint64(n), w)
		if err != nil {
			return err
		}
		received += n
		atomic.AddUint64(&d.received, n)
	}

	return nil
}

// newDownload initializes and returns a download object.
func (f *file) newDownload(hosts []fetcher, destination string) *download {
	return &download{
		erasureCode: f.erasureCode,
		chunkSize:   f.chunkSize(),
		fileSize:    f.size,
		hosts:       hosts,

		startTime:   time.Now(),
		received:    0,
		siapath:     f.name,
		destination: destination,
	}
}

// Download downloads a file, identified by its path, to the destination
// specified.
func (r *Renter) Download(path, destination string) error {
	// Lookup the file associated with the nickname.
	lockID := r.mu.Lock()
	file, exists := r.files[path]
	r.mu.Unlock(lockID)
	if !exists {
		return errors.New("no file with that path")
	}

	if !r.wallet.Unlocked() {
		return errors.New("wallet must be unlocked before downloading")
	}

	// Copy the file's metadata
	// TODO: this is ugly because we only have the Contracts method for
	// looking up contracts.
	contracts := make(map[*fileContract]contractor.Contract)
	file.mu.RLock()
	for _, c := range r.hostContractor.Contracts() {
		fc, ok := file.contracts[c.ID]
		if ok {
			contracts[&fc] = c
		}
	}
	file.mu.RUnlock()

	if len(contracts) == 0 {
		return errors.New("no record of that file's contracts")
	}

	// Initiate connections to each host.
	var hosts []fetcher
	var errs []string
	for fc, c := range contracts {
		// TODO: connect in parallel
		d, err := r.hostContractor.Downloader(c)
		if err != nil {
			errs = append(errs, fmt.Sprintf("\t%v: %v", c.IP, err))
			continue
		}
		defer d.Close()
		hosts = append(hosts, newHostFetcher(d, *fc, file.masterKey))
	}
	if len(hosts) == 0 {
		return errors.New("Could not connect to any hosts:\n" + strings.Join(errs, "\n"))
	}

	// Check that this host set is sufficient to download the file.
	err := checkHosts(hosts, file.erasureCode.MinPieces(), file.numChunks())
	if err != nil {
		return err
	}

	// Create file on disk with the correct permissions.
	perm := os.FileMode(file.mode)
	if perm == 0 {
		// sane default
		perm = 0666
	}
	f, err := os.OpenFile(destination, os.O_CREATE|os.O_RDWR|os.O_TRUNC, perm)
	if err != nil {
		return err
	}
	defer f.Close()

	// Create the download object.
	d := file.newDownload(hosts, destination)

	// Add the download to the download queue.
	lockID = r.mu.Lock()
	r.downloadQueue = append(r.downloadQueue, d)
	r.mu.Unlock(lockID)

	// Perform download.
	err = d.run(f)
	if err != nil {
		// File could not be downloaded; delete the copy on disk.
		os.Remove(destination)
		return err
	}

	return nil
}

// DownloadQueue returns the list of downloads in the queue.
func (r *Renter) DownloadQueue() []modules.DownloadInfo {
	lockID := r.mu.RLock()
	defer r.mu.RUnlock(lockID)

	// order from most recent to least recent
	downloads := make([]modules.DownloadInfo, len(r.downloadQueue))
	for i := range r.downloadQueue {
		d := r.downloadQueue[len(r.downloadQueue)-i-1]
		downloads[i] = modules.DownloadInfo{
			SiaPath:     d.siapath,
			Destination: d.destination,
			Filesize:    d.fileSize,
			Received:    atomic.LoadUint64(&d.received),
			StartTime:   d.startTime,
		}
	}
	return downloads
}
<|endoftext|>


<commit_before>package converter

import (
	"fmt"
	"strings"

	"github.com/getkin/kin-openapi/openapi3"
	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/protoc-gen-go/descriptor"
	"github.com/micro/micro/v3/service/logger"
)

const (
	openAPIFormatByte     = "byte"
	openAPIFormatDateTime = "date-time"
	openAPIFormatDouble   = "double"
	openAPIFormatInt32    = "int32"
	openAPIFormatInt64    = "int64"
	openAPITypeArray      = "array"
	openAPITypeBoolean    = "boolean"
	openAPITypeNumber     = "number"
	openAPITypeObject     = "object"
	openAPITypeString     = "string"
)

var (
	globalPkg = &ProtoPackage{
		name:     "",
		parent:   nil,
		children: make(map[string]*ProtoPackage),
		types:    make(map[string]*descriptor.DescriptorProto),
	}

	wellKnownTypes = map[string]bool{
		"DoubleValue": true,
		"FloatValue":  true,
		"Int64Value":  true,
		"UInt64Value": true,
		"Int32Value":  true,
		"UInt32Value": true,
		"BoolValue":   true,
		"StringValue": true,
		"BytesValue":  true,
		"Value":       true,
	}
)

func (c *Converter) registerType(pkgName *string, msg *descriptor.DescriptorProto) {
	pkg := globalPkg
	if pkgName != nil {
		for _, node := range strings.Split(*pkgName, ".") {
			if pkg == globalPkg && node == "" {
				// Skips leading "."
				continue
			}
			child, ok := pkg.children[node]
			if !ok {
				child = &ProtoPackage{
					name:     pkg.name + "." + node,
					parent:   pkg,
					children: make(map[string]*ProtoPackage),
					types:    make(map[string]*descriptor.DescriptorProto),
				}
				pkg.children[node] = child
			}
			pkg = child
		}
	}
	pkg.types[msg.GetName()] = msg
}

func (c *Converter) relativelyLookupNestedType(desc *descriptor.DescriptorProto, name string) (*descriptor.DescriptorProto, bool) {
	components := strings.Split(name, ".")
componentLoop:
	for _, component := range components {
		for _, nested := range desc.GetNestedType() {
			if nested.GetName() == component {
				desc = nested
				continue componentLoop
			}
		}
		logger.Infof("no such nested message (%s.%s)", component, desc.GetName())
		return nil, false
	}
	return desc, true
}

// Convert a proto "field" (essentially a type-switch with some recursion):
func (c *Converter) convertField(curPkg *ProtoPackage, desc *descriptor.FieldDescriptorProto, msg *descriptor.DescriptorProto) (*openapi3.Schema, error) {

	// Prepare a new jsonschema.Type for our eventual return value:
	componentSchema := &openapi3.Schema{}

	// Generate a description from src comments (if available)
	if src := c.sourceInfo.GetField(desc); src != nil {
		componentSchema.Description = formatDescription(src)
	}

	// Switch the types, and pick a JSONSchema equivalent:
	switch desc.GetType() {
	case descriptor.FieldDescriptorProto_TYPE_DOUBLE,
		descriptor.FieldDescriptorProto_TYPE_FLOAT:
		componentSchema.Type = openAPITypeNumber
		componentSchema.Format = openAPIFormatDouble

	case descriptor.FieldDescriptorProto_TYPE_INT32,
		descriptor.FieldDescriptorProto_TYPE_UINT32,
		descriptor.FieldDescriptorProto_TYPE_FIXED32,
		descriptor.FieldDescriptorProto_TYPE_SFIXED32,
		descriptor.FieldDescriptorProto_TYPE_SINT32:
		componentSchema.Type = openAPITypeNumber
		componentSchema.Format = openAPIFormatInt32

	case descriptor.FieldDescriptorProto_TYPE_INT64,
		descriptor.FieldDescriptorProto_TYPE_UINT64,
		descriptor.FieldDescriptorProto_TYPE_FIXED64,
		descriptor.FieldDescriptorProto_TYPE_SFIXED64,
		descriptor.FieldDescriptorProto_TYPE_SINT64:
		componentSchema.Type = openAPITypeNumber
		componentSchema.Format = openAPIFormatInt64

	case descriptor.FieldDescriptorProto_TYPE_STRING:
		componentSchema.Type = openAPITypeString

	case descriptor.FieldDescriptorProto_TYPE_BYTES:
		componentSchema.Type = openAPITypeString
		componentSchema.Format = openAPIFormatByte

	case descriptor.FieldDescriptorProto_TYPE_ENUM:
		componentSchema.Type = "string"

		// Go through all the enums we have, see if we can match any to this field by name:
		for _, enumDescriptor := range msg.GetEnumType() {

			// Each one has several values:
			for _, enumValue := range enumDescriptor.Value {

				// Figure out the entire name of this field:
				fullFieldName := fmt.Sprintf(".%v.%v", *msg.Name, *enumDescriptor.Name)

				// If we find ENUM values for this field then put them into the JSONSchema list of allowed ENUM values:
				if strings.HasSuffix(desc.GetTypeName(), fullFieldName) {
					componentSchema.Enum = append(componentSchema.Enum, *enumValue.Name)
				}
			}
		}

	case descriptor.FieldDescriptorProto_TYPE_BOOL:
		componentSchema.Type = openAPITypeBoolean

	case descriptor.FieldDescriptorProto_TYPE_GROUP, descriptor.FieldDescriptorProto_TYPE_MESSAGE:
		switch desc.GetTypeName() {
		case ".google.protobuf.Timestamp":
			componentSchema.Type = openAPITypeString
			componentSchema.Format = openAPIFormatDateTime
		default:
			componentSchema.Type = openAPITypeObject
		}

	default:
		return nil, fmt.Errorf("unrecognized field type: %s", desc.GetType().String())
	}

	isList := false
	if c.plug != nil && len(c.plug.Files) > 0 {
		for _, message := range c.plug.Files[0].Messages {
			for _, f := range message.Fields {
				parts := strings.Split(f.GoIdent.GoName, "_")
				messageName := parts[0]
				if messageName != *msg.Name {
					continue
				}
				fieldName := parts[1]
				if strings.ToLower(fieldName) == *desc.Name {
					isList = f.Desc.IsList()
				}
			}
		}
	}

	// Recurse array of primitive types:
	if isList && componentSchema.Type != openAPITypeObject {
		componentSchema.Items = &openapi3.SchemaRef{
			Value: &openapi3.Schema{
				Type: componentSchema.Type,
			},
		}
		componentSchema.Type = openAPITypeArray
		return componentSchema, nil
	}

	// Recurse nested objects / arrays of objects (if necessary):
	if componentSchema.Type == openAPITypeObject {

		recordType, pkgName, ok := c.lookupType(curPkg, desc.GetTypeName())
		if !ok {
			return nil, fmt.Errorf("no such message type named %s", desc.GetTypeName())
		}

		// Recurse the recordType:
		recursedComponentSchema, err := c.recursiveConvertMessageType(curPkg, recordType, pkgName)
		if err != nil {
			return nil, err
		}

		// Maps, arrays, and objects are structured in different ways:
		switch {
		// Arrays:
		case isList:
			componentSchema.Items = &openapi3.SchemaRef{
				Value: &openapi3.Schema{
					Type:       openAPITypeObject,
					Properties: recursedComponentSchema.Properties,
				},
			}

			componentSchema.Type = openAPITypeArray
		// Maps:
		case recordType.Options.GetMapEntry():
			logger.Tracef("Found a map (%s.%s)", *msg.Name, recordType.GetName())
			componentSchema.Type = openAPITypeObject
			//componentSchema.AdditionalProperties = openapi3.NewSchemaRef("", recursedComponentSchema)
		// Objects:
		default:
			componentSchema.Properties = recursedComponentSchema.Properties
			// recursedComponentSchemaRef := fmt.Sprintf("#/components/schemas/%s", recursedComponentSchema.Title)
			// componentSchema.Properties = openapi3.NewSchemaRef(recursedComponentSchemaRef, nil)
		}
	}

	return componentSchema, nil
}

// Converts a proto "MESSAGE" into an OpenAPI schema:
func (c *Converter) convertMessageType(curPkg *ProtoPackage, msg *descriptor.DescriptorProto) (*openapi3.Schema, error) {

	// main schema for the message
	rootType, err := c.recursiveConvertMessageType(curPkg, msg, "")
	if err != nil {
		return nil, err
	}

	return rootType, nil
}

type nameAndCounter struct {
	name    string
	counter int
}

func (c *Converter) recursiveConvertMessageType(curPkg *ProtoPackage, msg *descriptor.DescriptorProto, pkgName string) (*openapi3.Schema, error) {
	if msg.Name != nil && wellKnownTypes[*msg.Name] && pkgName == ".google.protobuf" {
		componentSchema := &openapi3.Schema{
			Title: msg.GetName(),
		}
		switch *msg.Name {
		case "DoubleValue", "FloatValue":
			componentSchema.Type = openAPITypeNumber
			componentSchema.Format = openAPIFormatDouble
		case "Int32Value", "UInt32Value":
			componentSchema.Type = openAPITypeNumber
			componentSchema.Format = openAPIFormatInt32
		case "Int64Value", "UInt64Value":
			componentSchema.Type = openAPITypeNumber
			componentSchema.Format = openAPIFormatInt64
		case "BoolValue":
			componentSchema.Type = openAPITypeBoolean
		case "StringValue":
			componentSchema.Type = openAPITypeString
		case "BytesValue":
			componentSchema.Type = openAPITypeString
			componentSchema.Format = openAPIFormatByte
		case "Value":
			componentSchema.Type = openAPITypeObject
		}
		return componentSchema, nil
	}

	// Prepare a new jsonschema:
	componentSchema := &openapi3.Schema{
		Properties: make(map[string]*openapi3.SchemaRef),
		Title:      msg.GetName(),
		Type:       openAPITypeObject,
	}

	// Generate a description from src comments (if available)
	if src := c.sourceInfo.GetMessage(msg); src != nil {
		componentSchema.Description = formatDescription(src)
	}

	logger.Tracef("Converting message (%s)", proto.MarshalTextString(msg))

	// Recurse each field:
	for _, fieldDesc := range msg.GetField() {
		recursedComponentSchema, err := c.convertField(curPkg, fieldDesc, msg)
		if err != nil {
			logger.Errorf("Failed to convert field (%s.%s): %v", msg.GetName(), fieldDesc.GetName(), err)
			return nil, err
		}
		logger.Tracef("Converted field: %s => %s", fieldDesc.GetName(), recursedComponentSchema.Type)

		// Add it to the properties (by its JSON name):
		componentSchema.Properties[fieldDesc.GetJsonName()] = openapi3.NewSchemaRef("", recursedComponentSchema)
	}

	return componentSchema, nil
}

func formatDescription(sl *descriptor.SourceCodeInfo_Location) string {
	var lines []string
	for _, str := range sl.GetLeadingDetachedComments() {
		if s := strings.TrimSpace(str); s != "" {
			lines = append(lines, s)
		}
	}
	if s := strings.TrimSpace(sl.GetLeadingComments()); s != "" {
		lines = append(lines, s)
	}
	if s := strings.TrimSpace(sl.GetTrailingComments()); s != "" {
		lines = append(lines, s)
	}
	return strings.Join(lines, "\n\n")
}
<commit_msg>Ignore protobuf structs when generating openapi (#1815)<commit_after>package converter

import (
	"fmt"
	"strings"

	"github.com/getkin/kin-openapi/openapi3"
	"github.com/golang/protobuf/proto"
	"github.com/golang/protobuf/protoc-gen-go/descriptor"
	"github.com/micro/micro/v3/service/logger"
	"google.golang.org/protobuf/compiler/protogen"
)

const (
	openAPIFormatByte     = "byte"
	openAPIFormatDateTime = "date-time"
	openAPIFormatDouble   = "double"
	openAPIFormatInt32    = "int32"
	openAPIFormatInt64    = "int64"
	openAPITypeArray      = "array"
	openAPITypeBoolean    = "boolean"
	openAPITypeNumber     = "number"
	openAPITypeObject     = "object"
	openAPITypeString     = "string"
)

var (
	globalPkg = &ProtoPackage{
		name:     "",
		parent:   nil,
		children: make(map[string]*ProtoPackage),
		types:    make(map[string]*descriptor.DescriptorProto),
	}

	wellKnownTypes = map[string]bool{
		"DoubleValue": true,
		"FloatValue":  true,
		"Int64Value":  true,
		"UInt64Value": true,
		"Int32Value":  true,
		"UInt32Value": true,
		"BoolValue":   true,
		"StringValue": true,
		"BytesValue":  true,
		"Value":       true,
	}
)

func (c *Converter) registerType(pkgName *string, msg *descriptor.DescriptorProto) {
	pkg := globalPkg
	if pkgName != nil {
		for _, node := range strings.Split(*pkgName, ".") {
			if pkg == globalPkg && node == "" {
				// Skips leading "."
				continue
			}
			child, ok := pkg.children[node]
			if !ok {
				child = &ProtoPackage{
					name:     pkg.name + "." + node,
					parent:   pkg,
					children: make(map[string]*ProtoPackage),
					types:    make(map[string]*descriptor.DescriptorProto),
				}
				pkg.children[node] = child
			}
			pkg = child
		}
	}
	pkg.types[msg.GetName()] = msg
}

func (c *Converter) relativelyLookupNestedType(desc *descriptor.DescriptorProto, name string) (*descriptor.DescriptorProto, bool) {
	components := strings.Split(name, ".")
componentLoop:
	for _, component := range components {
		for _, nested := range desc.GetNestedType() {
			if nested.GetName() == component {
				desc = nested
				continue componentLoop
			}
		}
		logger.Infof("no such nested message (%s.%s)", component, desc.GetName())
		return nil, false
	}
	return desc, true
}

// Convert a proto "field" (essentially a type-switch with some recursion):
func (c *Converter) convertField(curPkg *ProtoPackage, desc *descriptor.FieldDescriptorProto, msg *descriptor.DescriptorProto) (*openapi3.Schema, error) {

	// Prepare a new jsonschema.Type for our eventual return value:
	componentSchema := &openapi3.Schema{}

	// Generate a description from src comments (if available)
	if src := c.sourceInfo.GetField(desc); src != nil {
		componentSchema.Description = formatDescription(src)
	}

	// Switch the types, and pick a JSONSchema equivalent:
	switch desc.GetType() {
	case descriptor.FieldDescriptorProto_TYPE_DOUBLE,
		descriptor.FieldDescriptorProto_TYPE_FLOAT:
		componentSchema.Type = openAPITypeNumber
		componentSchema.Format = openAPIFormatDouble

	case descriptor.FieldDescriptorProto_TYPE_INT32,
		descriptor.FieldDescriptorProto_TYPE_UINT32,
		descriptor.FieldDescriptorProto_TYPE_FIXED32,
		descriptor.FieldDescriptorProto_TYPE_SFIXED32,
		descriptor.FieldDescriptorProto_TYPE_SINT32:
		componentSchema.Type = openAPITypeNumber
		componentSchema.Format = openAPIFormatInt32

	case descriptor.FieldDescriptorProto_TYPE_INT64,
		descriptor.FieldDescriptorProto_TYPE_UINT64,
		descriptor.FieldDescriptorProto_TYPE_FIXED64,
		descriptor.FieldDescriptorProto_TYPE_SFIXED64,
		descriptor.FieldDescriptorProto_TYPE_SINT64:
		componentSchema.Type = openAPITypeNumber
		componentSchema.Format = openAPIFormatInt64

	case descriptor.FieldDescriptorProto_TYPE_STRING:
		componentSchema.Type = openAPITypeString

	case descriptor.FieldDescriptorProto_TYPE_BYTES:
		componentSchema.Type = openAPITypeString
		componentSchema.Format = openAPIFormatByte

	case descriptor.FieldDescriptorProto_TYPE_ENUM:
		componentSchema.Type = "string"

		// Go through all the enums we have, see if we can match any to this field by name:
		for _, enumDescriptor := range msg.GetEnumType() {

			// Each one has several values:
			for _, enumValue := range enumDescriptor.Value {

				// Figure out the entire name of this field:
				fullFieldName := fmt.Sprintf(".%v.%v", *msg.Name, *enumDescriptor.Name)

				// If we find ENUM values for this field then put them into the JSONSchema list of allowed ENUM values:
				if strings.HasSuffix(desc.GetTypeName(), fullFieldName) {
					componentSchema.Enum = append(componentSchema.Enum, *enumValue.Name)
				}
			}
		}

	case descriptor.FieldDescriptorProto_TYPE_BOOL:
		componentSchema.Type = openAPITypeBoolean

	case descriptor.FieldDescriptorProto_TYPE_GROUP, descriptor.FieldDescriptorProto_TYPE_MESSAGE:
		switch desc.GetTypeName() {
		case ".google.protobuf.Timestamp":
			componentSchema.Type = openAPITypeString
			componentSchema.Format = openAPIFormatDateTime
		default:
			componentSchema.Type = openAPITypeObject
		}

	default:
		return nil, fmt.Errorf("unrecognized field type: %s", desc.GetType().String())
	}

	isList := false
	var field *protogen.Field
	if c.plug != nil && len(c.plug.Files) > 0 {
		for _, file := range c.plug.Files {
			for _, message := range file.Messages {
				for _, f := range message.Fields {
					parts := strings.Split(string(f.GoIdent.GoName), "_")
					messageName := parts[0]

					if messageName != *msg.Name {
						continue
					}
					fieldName := parts[1]
					if strings.ToLower(fieldName) == *desc.Name {
						isList = f.Desc.IsList()
						field = f
					}
				}
			}
		}

	}
	// Recurse array of primitive types:
	if isList && componentSchema.Type != openAPITypeObject {
		componentSchema.Items = &openapi3.SchemaRef{
			Value: &openapi3.Schema{
				Type: componentSchema.Type,
			},
		}
		componentSchema.Type = openAPITypeArray
		return componentSchema, nil
	}

	// Recurse nested objects / arrays of objects (if necessary):
	if componentSchema.Type == openAPITypeObject {

		recordType, pkgName, ok := c.lookupType(curPkg, desc.GetTypeName())
		if !ok {
			return nil, fmt.Errorf("no such message type named %s", desc.GetTypeName())
		}

		// Recurse the recordType:
		recursedComponentSchema, err := c.recursiveConvertMessageType(curPkg, recordType, pkgName)
		if err != nil {
			return nil, err
		}

		// Maps, arrays, and objects are structured in different ways:
		switch {
		case field != nil && field.Desc.Message().FullName() == "google.protobuf.Struct":
			componentSchema.Type = openAPITypeObject
		// Arrays:
		case isList:
			componentSchema.Items = &openapi3.SchemaRef{
				Value: &openapi3.Schema{
					Type:       openAPITypeObject,
					Properties: recursedComponentSchema.Properties,
				},
			}

			componentSchema.Type = openAPITypeArray
		// Maps:
		case recordType.Options.GetMapEntry():
			logger.Tracef("Found a map (%s.%s)", *msg.Name, recordType.GetName())
			componentSchema.Type = openAPITypeObject
			//componentSchema.AdditionalProperties = openapi3.NewSchemaRef("", recursedComponentSchema)
		// Objects:
		default:
			componentSchema.Properties = recursedComponentSchema.Properties
			// recursedComponentSchemaRef := fmt.Sprintf("#/components/schemas/%s", recursedComponentSchema.Title)
			// componentSchema.Properties = openapi3.NewSchemaRef(recursedComponentSchemaRef, nil)
		}
	}

	return componentSchema, nil
}

// Converts a proto "MESSAGE" into an OpenAPI schema:
func (c *Converter) convertMessageType(curPkg *ProtoPackage, msg *descriptor.DescriptorProto) (*openapi3.Schema, error) {

	// main schema for the message
	rootType, err := c.recursiveConvertMessageType(curPkg, msg, "")
	if err != nil {
		return nil, err
	}

	return rootType, nil
}

type nameAndCounter struct {
	name    string
	counter int
}

func (c *Converter) recursiveConvertMessageType(curPkg *ProtoPackage, msg *descriptor.DescriptorProto, pkgName string) (*openapi3.Schema, error) {
	if msg.Name != nil && wellKnownTypes[*msg.Name] && pkgName == ".google.protobuf" {
		componentSchema := &openapi3.Schema{
			Title: msg.GetName(),
		}
		switch *msg.Name {
		case "DoubleValue", "FloatValue":
			componentSchema.Type = openAPITypeNumber
			componentSchema.Format = openAPIFormatDouble
		case "Int32Value", "UInt32Value":
			componentSchema.Type = openAPITypeNumber
			componentSchema.Format = openAPIFormatInt32
		case "Int64Value", "UInt64Value":
			componentSchema.Type = openAPITypeNumber
			componentSchema.Format = openAPIFormatInt64
		case "BoolValue":
			componentSchema.Type = openAPITypeBoolean
		case "StringValue":
			componentSchema.Type = openAPITypeString
		case "BytesValue":
			componentSchema.Type = openAPITypeString
			componentSchema.Format = openAPIFormatByte
		case "Value":
			componentSchema.Type = openAPITypeObject
		}
		return componentSchema, nil
	}

	// Prepare a new jsonschema:
	componentSchema := &openapi3.Schema{
		Properties: make(map[string]*openapi3.SchemaRef),
		Title:      msg.GetName(),
		Type:       openAPITypeObject,
	}

	// Generate a description from src comments (if available)
	if src := c.sourceInfo.GetMessage(msg); src != nil {
		componentSchema.Description = formatDescription(src)
	}

	logger.Tracef("Converting message (%s)", proto.MarshalTextString(msg))

	// Recurse each field:
	for _, fieldDesc := range msg.GetField() {
		recursedComponentSchema, err := c.convertField(curPkg, fieldDesc, msg)
		if err != nil {
			logger.Errorf("Failed to convert field (%s.%s): %v", msg.GetName(), fieldDesc.GetName(), err)
			return nil, err
		}
		logger.Tracef("Converted field: %s => %s", fieldDesc.GetName(), recursedComponentSchema.Type)

		// Add it to the properties (by its JSON name):
		componentSchema.Properties[fieldDesc.GetJsonName()] = openapi3.NewSchemaRef("", recursedComponentSchema)
	}

	return componentSchema, nil
}

func formatDescription(sl *descriptor.SourceCodeInfo_Location) string {
	var lines []string
	for _, str := range sl.GetLeadingDetachedComments() {
		if s := strings.TrimSpace(str); s != "" {
			lines = append(lines, s)
		}
	}
	if s := strings.TrimSpace(sl.GetLeadingComments()); s != "" {
		lines = append(lines, s)
	}
	if s := strings.TrimSpace(sl.GetTrailingComments()); s != "" {
		lines = append(lines, s)
	}
	return strings.Join(lines, "\n\n")
}
<|endoftext|>


<commit_before>package finalize

import (
	"bufio"
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/cloudfoundry/libbuildpack"
)

type Cache interface {
	Initialize() error
	Restore() error
	Save() error
}

type Command interface {
	Execute(string, io.Writer, io.Writer, string, ...string) error
}

type Manifest interface {
	RootDir() string
}

type NPM interface {
	Build() error
	Rebuild() error
}

type Stager interface {
	BuildDir() string
	DepDir() string
}

type Yarn interface {
	Build() error
}

type Finalizer struct {
	Stager             Stager
	Command            Command
	Cache              Cache
	Log                *libbuildpack.Logger
	Logfile            *os.File
	PreBuild           string
	PostBuild          string
	NPM                NPM
	NPMRebuild         bool
	Yarn               Yarn
	UseYarn            bool
	Manifest           Manifest
	StartScript        string
	HasDevDependencies bool
}

func Run(f *Finalizer) error {
	if err := f.ReadPackageJSON(); err != nil {
		f.Log.Error("Failed parsing package.json: %s", err.Error())
		return err
	}

	if err := f.TipVendorDependencies(); err != nil {
		f.Log.Error(err.Error())
		return err
	}

	f.ListNodeConfig(os.Environ())

	if err := f.Cache.Initialize(); err != nil {
		f.Log.Error("Unable to initialize cache: %s", err.Error())
		return err
	}

	if err := f.Cache.Restore(); err != nil {
		f.Log.Error("Unable to restore cache: %s", err.Error())
		return err
	}

	defer func() {
		f.Logfile.Sync()
		f.WarnUntrackedDependencies()
		f.WarnMissingDevDeps()
	}()

	if err := f.BuildDependencies(); err != nil {
		f.Log.Error("Unable to build dependencies: %s", err.Error())
		return err
	}

	if err := f.Cache.Save(); err != nil {
		f.Log.Error("Unable to save cache: %s", err.Error())
		return err
	}

	if err := f.CopyProfileScripts(); err != nil {
		f.Log.Error("Unable to copy profile.d scripts: %s", err.Error())
		return err
	}

	f.ListDependencies()

	if err := f.WarnNoStart(); err != nil {
		f.Log.Error(err.Error())
		return err
	}

	if err := f.Logfile.Sync(); err != nil {
		f.Log.Error(err.Error())
		return err
	}

	if err := f.WarnUnmetDependencies(); err != nil {
		f.Log.Error(err.Error())
		return err
	}

	return nil
}

func (f *Finalizer) ReadPackageJSON() error {
	var err error
	var p struct {
		Scripts struct {
			PreBuild    string `json:"heroku-prebuild"`
			PostBuild   string `json:"heroku-postbuild"`
			StartScript string `json:"start"`
		} `json:"scripts"`
		DevDependencies map[string]string `json:"devDependencies"`
	}

	if f.UseYarn, err = libbuildpack.FileExists(filepath.Join(f.Stager.BuildDir(), "yarn.lock")); err != nil {
		return err
	}

	if f.NPMRebuild, err = libbuildpack.FileExists(filepath.Join(f.Stager.BuildDir(), "node_modules")); err != nil {
		return err
	}

	if err := libbuildpack.NewJSON().Load(filepath.Join(f.Stager.BuildDir(), "package.json"), &p); err != nil {
		if os.IsNotExist(err) {
			f.Log.Warning("No package.json found")
			return nil
		} else {
			return err
		}
	}

	f.HasDevDependencies = (len(p.DevDependencies) > 0)
	f.PreBuild = p.Scripts.PreBuild
	f.PostBuild = p.Scripts.PostBuild
	f.StartScript = p.Scripts.StartScript

	return nil
}

func (f *Finalizer) TipVendorDependencies() error {
	subdirs, err := hasSubdirs(filepath.Join(f.Stager.BuildDir(), "node_modules"))
	if err != nil {
		return err
	}
	if !subdirs {
		f.Log.Protip("It is recommended to vendor the application's Node.js dependencies",
			"http://docs.cloudfoundry.org/buildpacks/node/index.html#vendoring")
	}

	return nil
}

func (f *Finalizer) ListNodeConfig(environment []string) {
	npmConfigProductionTrue := false
	nodeEnv := "production"

	for _, env := range environment {
		if strings.HasPrefix(env, "NPM_CONFIG_") || strings.HasPrefix(env, "YARN_") || strings.HasPrefix(env, "NODE_") {
			f.Log.Info(env)
		}

		if env == "NPM_CONFIG_PRODUCTION=true" {
			npmConfigProductionTrue = true
		}

		if strings.HasPrefix(env, "NODE_ENV=") {
			nodeEnv = env[9:]
		}
	}

	if npmConfigProductionTrue && nodeEnv != "production" {
		f.Log.Info("npm scripts will see NODE_ENV=production (not '%s')\nhttps://docs.npmjs.com/misc/config#production", nodeEnv)
	}
}

func (f *Finalizer) BuildDependencies() error {
	var tool string
	if f.UseYarn {
		tool = "yarn"
	} else {
		tool = "npm"
	}

	f.Log.BeginStep("Building dependencies")

	if err := f.runPrebuild(tool); err != nil {
		return err
	}

	if f.UseYarn {
		if err := f.Yarn.Build(); err != nil {
			return err
		}
	} else {
		if f.NPMRebuild {
			f.Log.Info("Prebuild detected (node_modules already exists)")
			if err := f.NPM.Rebuild(); err != nil {
				return err
			}
		} else {
			if err := f.NPM.Build(); err != nil {
				return err
			}
		}
	}

	if err := f.runPostbuild(tool); err != nil {
		return err
	}

	return nil
}

func (f *Finalizer) CopyProfileScripts() error {
	profiledDir := filepath.Join(f.Stager.DepDir(), "profile.d")
	if err := os.MkdirAll(profiledDir, 0755); err != nil {
		return err
	}
	return libbuildpack.CopyDirectory(filepath.Join(f.Manifest.RootDir(), "profile"), profiledDir)
}

func (f *Finalizer) ListDependencies() {
	if os.Getenv("NODE_VERBOSE") != "true" {
		return
	}

	if f.UseYarn {
		_ = f.Command.Execute(f.Stager.BuildDir(), f.Log.Output(), ioutil.Discard, "yarn", "list", "--depth=0")
	} else {
		_ = f.Command.Execute(f.Stager.BuildDir(), f.Log.Output(), ioutil.Discard, "npm", "ls", "--depth=0")
	}
}

func (f *Finalizer) WarnNoStart() error {
	procfileExists, err := libbuildpack.FileExists(filepath.Join(f.Stager.BuildDir(), "Procfile"))
	if err != nil {
		return err
	}
	serverJsExists, err := libbuildpack.FileExists(filepath.Join(f.Stager.BuildDir(), "server.js"))
	if err != nil {
		return err
	}

	if !procfileExists && !serverJsExists && f.StartScript == "" {
		warning := "This app may not specify any way to start a node process\n"
		warning += "See: https://docs.cloudfoundry.org/buildpacks/node/node-tips.html#start"
		f.Log.Warning(warning)
	}

	return nil
}

func (f *Finalizer) WarnUnmetDependencies() error {
	if unmet, err := fileHasString(f.Logfile.Name(), "unmet dependency", "unmet peer dependency"); err != nil {
		return err
	} else if !unmet {
		return nil
	}

	pkgMan := "npm"
	if f.UseYarn {
		pkgMan = "yarn"
	}

	warning := "Unmet dependencies don't fail " + pkgMan + " install but may cause runtime issues\n"
	warning += "See: https://github.com/npm/npm/issues/7494"
	f.Log.Warning(warning)
	return nil
}

func (f *Finalizer) WarnUntrackedDependencies() error {
	for _, command := range []string{"grunt", "bower", "gulp"} {
		if notFound, err := fileHasString(f.Logfile.Name(), command+": not found", command+": command not found"); err != nil {
			return err
		} else if notFound {
			f.Log.Warning(strings.Title(command) + " may not be tracked in package.json")
		}
	}

	return nil
}

func (f *Finalizer) WarnMissingDevDeps() error {
	if noModule, err := fileHasString(f.Logfile.Name(), "cannot find module"); err != nil {
		return err
	} else if !noModule {
		return nil
	}

	warning := "A module may be missing from 'dependencies' in package.json"

	if os.Getenv("NPM_CONFIG_PRODUCTION") == "true" && f.HasDevDependencies {
		warning += "\nThis module may be specified in 'devDependencies' instead of 'dependencies'\n"
		warning += "See: https://devcenter.heroku.com/articles/nodejs-support#devdependencies"
	}

	f.Log.Warning(warning)
	return nil
}

func (f *Finalizer) runPrebuild(tool string) error {
	if f.PreBuild == "" {
		return nil
	}

	return f.runScript(f.PreBuild, tool)
}

func (f *Finalizer) runPostbuild(tool string) error {
	if f.PostBuild == "" {
		return nil
	}

	return f.runScript(f.PostBuild, tool)
}

func (f *Finalizer) runScript(script, tool string) error {
	args := []string{"run", script}
	if tool == "npm" {
		args = append(args, "--if-present")
	}

	f.Log.Info("Running %s (%s)", script, tool)

	return f.Command.Execute(f.Stager.BuildDir(), os.Stdout, os.Stderr, tool, args...)

}

func hasSubdirs(path string) (bool, error) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}

		return false, err
	}

	for _, file := range files {
		if file.IsDir() {
			return true, nil
		}
	}

	return false, nil
}

func (f *Finalizer) findVersion(binary string) (string, error) {
	buffer := new(bytes.Buffer)
	if err := f.Command.Execute("", buffer, ioutil.Discard, binary, "--version"); err != nil {
		return "", err
	}
	return strings.TrimSpace(buffer.String()), nil
}

func fileHasString(file string, patterns ...string) (bool, error) {
	f, err := os.Open(file)
	if err != nil {
		return false, err
	}
	defer f.Close()
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		for _, pat := range patterns {
			src := strings.ToLower(scanner.Text())

			if strings.Contains(src, pat) {
				return true, nil
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return false, err
	}
	return false, nil
}
<commit_msg>Fix error with bufio.Scanner when contents of files overruns buffer<commit_after>package finalize

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/cloudfoundry/libbuildpack"
)

type Cache interface {
	Initialize() error
	Restore() error
	Save() error
}

type Command interface {
	Execute(string, io.Writer, io.Writer, string, ...string) error
}

type Manifest interface {
	RootDir() string
}

type NPM interface {
	Build() error
	Rebuild() error
}

type Stager interface {
	BuildDir() string
	DepDir() string
}

type Yarn interface {
	Build() error
}

type Finalizer struct {
	Stager             Stager
	Command            Command
	Cache              Cache
	Log                *libbuildpack.Logger
	Logfile            *os.File
	PreBuild           string
	PostBuild          string
	NPM                NPM
	NPMRebuild         bool
	Yarn               Yarn
	UseYarn            bool
	Manifest           Manifest
	StartScript        string
	HasDevDependencies bool
}

func Run(f *Finalizer) error {
	if err := f.ReadPackageJSON(); err != nil {
		f.Log.Error("Failed parsing package.json: %s", err.Error())
		return err
	}

	if err := f.TipVendorDependencies(); err != nil {
		f.Log.Error(err.Error())
		return err
	}

	f.ListNodeConfig(os.Environ())

	if err := f.Cache.Initialize(); err != nil {
		f.Log.Error("Unable to initialize cache: %s", err.Error())
		return err
	}

	if err := f.Cache.Restore(); err != nil {
		f.Log.Error("Unable to restore cache: %s", err.Error())
		return err
	}

	defer func() {
		f.Logfile.Sync()
		f.WarnUntrackedDependencies()
		f.WarnMissingDevDeps()
	}()

	if err := f.BuildDependencies(); err != nil {
		f.Log.Error("Unable to build dependencies: %s", err.Error())
		return err
	}

	if err := f.Cache.Save(); err != nil {
		f.Log.Error("Unable to save cache: %s", err.Error())
		return err
	}

	if err := f.CopyProfileScripts(); err != nil {
		f.Log.Error("Unable to copy profile.d scripts: %s", err.Error())
		return err
	}

	f.ListDependencies()

	if err := f.WarnNoStart(); err != nil {
		f.Log.Error(err.Error())
		return err
	}

	if err := f.Logfile.Sync(); err != nil {
		f.Log.Error(err.Error())
		return err
	}

	if err := f.WarnUnmetDependencies(); err != nil {
		f.Log.Error(err.Error())
		return err
	}

	return nil
}

func (f *Finalizer) ReadPackageJSON() error {
	var err error
	var p struct {
		Scripts struct {
			PreBuild    string `json:"heroku-prebuild"`
			PostBuild   string `json:"heroku-postbuild"`
			StartScript string `json:"start"`
		} `json:"scripts"`
		DevDependencies map[string]string `json:"devDependencies"`
	}

	if f.UseYarn, err = libbuildpack.FileExists(filepath.Join(f.Stager.BuildDir(), "yarn.lock")); err != nil {
		return err
	}

	if f.NPMRebuild, err = libbuildpack.FileExists(filepath.Join(f.Stager.BuildDir(), "node_modules")); err != nil {
		return err
	}

	if err := libbuildpack.NewJSON().Load(filepath.Join(f.Stager.BuildDir(), "package.json"), &p); err != nil {
		if os.IsNotExist(err) {
			f.Log.Warning("No package.json found")
			return nil
		} else {
			return err
		}
	}

	f.HasDevDependencies = (len(p.DevDependencies) > 0)
	f.PreBuild = p.Scripts.PreBuild
	f.PostBuild = p.Scripts.PostBuild
	f.StartScript = p.Scripts.StartScript

	return nil
}

func (f *Finalizer) TipVendorDependencies() error {
	subdirs, err := hasSubdirs(filepath.Join(f.Stager.BuildDir(), "node_modules"))
	if err != nil {
		return err
	}
	if !subdirs {
		f.Log.Protip("It is recommended to vendor the application's Node.js dependencies",
			"http://docs.cloudfoundry.org/buildpacks/node/index.html#vendoring")
	}

	return nil
}

func (f *Finalizer) ListNodeConfig(environment []string) {
	npmConfigProductionTrue := false
	nodeEnv := "production"

	for _, env := range environment {
		if strings.HasPrefix(env, "NPM_CONFIG_") || strings.HasPrefix(env, "YARN_") || strings.HasPrefix(env, "NODE_") {
			f.Log.Info(env)
		}

		if env == "NPM_CONFIG_PRODUCTION=true" {
			npmConfigProductionTrue = true
		}

		if strings.HasPrefix(env, "NODE_ENV=") {
			nodeEnv = env[9:]
		}
	}

	if npmConfigProductionTrue && nodeEnv != "production" {
		f.Log.Info("npm scripts will see NODE_ENV=production (not '%s')\nhttps://docs.npmjs.com/misc/config#production", nodeEnv)
	}
}

func (f *Finalizer) BuildDependencies() error {
	var tool string
	if f.UseYarn {
		tool = "yarn"
	} else {
		tool = "npm"
	}

	f.Log.BeginStep("Building dependencies")

	if err := f.runPrebuild(tool); err != nil {
		return err
	}

	if f.UseYarn {
		if err := f.Yarn.Build(); err != nil {
			return err
		}
	} else {
		if f.NPMRebuild {
			f.Log.Info("Prebuild detected (node_modules already exists)")
			if err := f.NPM.Rebuild(); err != nil {
				return err
			}
		} else {
			if err := f.NPM.Build(); err != nil {
				return err
			}
		}
	}

	if err := f.runPostbuild(tool); err != nil {
		return err
	}

	return nil
}

func (f *Finalizer) CopyProfileScripts() error {
	profiledDir := filepath.Join(f.Stager.DepDir(), "profile.d")
	if err := os.MkdirAll(profiledDir, 0755); err != nil {
		return err
	}
	return libbuildpack.CopyDirectory(filepath.Join(f.Manifest.RootDir(), "profile"), profiledDir)
}

func (f *Finalizer) ListDependencies() {
	if os.Getenv("NODE_VERBOSE") != "true" {
		return
	}

	if f.UseYarn {
		_ = f.Command.Execute(f.Stager.BuildDir(), f.Log.Output(), ioutil.Discard, "yarn", "list", "--depth=0")
	} else {
		_ = f.Command.Execute(f.Stager.BuildDir(), f.Log.Output(), ioutil.Discard, "npm", "ls", "--depth=0")
	}
}

func (f *Finalizer) WarnNoStart() error {
	procfileExists, err := libbuildpack.FileExists(filepath.Join(f.Stager.BuildDir(), "Procfile"))
	if err != nil {
		return err
	}
	serverJsExists, err := libbuildpack.FileExists(filepath.Join(f.Stager.BuildDir(), "server.js"))
	if err != nil {
		return err
	}

	if !procfileExists && !serverJsExists && f.StartScript == "" {
		warning := "This app may not specify any way to start a node process\n"
		warning += "See: https://docs.cloudfoundry.org/buildpacks/node/node-tips.html#start"
		f.Log.Warning(warning)
	}

	return nil
}

func (f *Finalizer) WarnUnmetDependencies() error {
	if unmet, err := fileHasString(f.Logfile.Name(), "unmet dependency", "unmet peer dependency"); err != nil {
		return err
	} else if !unmet {
		return nil
	}

	pkgMan := "npm"
	if f.UseYarn {
		pkgMan = "yarn"
	}

	warning := "Unmet dependencies don't fail " + pkgMan + " install but may cause runtime issues\n"
	warning += "See: https://github.com/npm/npm/issues/7494"
	f.Log.Warning(warning)
	return nil
}

func (f *Finalizer) WarnUntrackedDependencies() error {
	for _, command := range []string{"grunt", "bower", "gulp"} {
		if notFound, err := fileHasString(f.Logfile.Name(), command+": not found", command+": command not found"); err != nil {
			return err
		} else if notFound {
			f.Log.Warning(strings.Title(command) + " may not be tracked in package.json")
		}
	}

	return nil
}

func (f *Finalizer) WarnMissingDevDeps() error {
	if noModule, err := fileHasString(f.Logfile.Name(), "cannot find module"); err != nil {
		return err
	} else if !noModule {
		return nil
	}

	warning := "A module may be missing from 'dependencies' in package.json"

	if os.Getenv("NPM_CONFIG_PRODUCTION") == "true" && f.HasDevDependencies {
		warning += "\nThis module may be specified in 'devDependencies' instead of 'dependencies'\n"
		warning += "See: https://devcenter.heroku.com/articles/nodejs-support#devdependencies"
	}

	f.Log.Warning(warning)
	return nil
}

func (f *Finalizer) runPrebuild(tool string) error {
	if f.PreBuild == "" {
		return nil
	}

	return f.runScript(f.PreBuild, tool)
}

func (f *Finalizer) runPostbuild(tool string) error {
	if f.PostBuild == "" {
		return nil
	}

	return f.runScript(f.PostBuild, tool)
}

func (f *Finalizer) runScript(script, tool string) error {
	args := []string{"run", script}
	if tool == "npm" {
		args = append(args, "--if-present")
	}

	f.Log.Info("Running %s (%s)", script, tool)

	return f.Command.Execute(f.Stager.BuildDir(), os.Stdout, os.Stderr, tool, args...)

}

func hasSubdirs(path string) (bool, error) {
	files, err := ioutil.ReadDir(path)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}

		return false, err
	}

	for _, file := range files {
		if file.IsDir() {
			return true, nil
		}
	}

	return false, nil
}

func (f *Finalizer) findVersion(binary string) (string, error) {
	buffer := new(bytes.Buffer)
	if err := f.Command.Execute("", buffer, ioutil.Discard, binary, "--version"); err != nil {
		return "", err
	}
	return strings.TrimSpace(buffer.String()), nil
}

func fileHasString(file string, patterns ...string) (bool, error) {
	f, err := os.Open(file)
	if err != nil {
		return false, err
	}
	defer f.Close()
	reader := bufio.NewReader(f)
	line, err := reader.ReadString('\n')
	for err == nil {
		src := strings.ToLower(line)
		for _, pat := range patterns {
			if strings.Contains(src, pat) {
				return true, nil
			}
		}
		line, err = reader.ReadString('\n')
	}
	if err != io.EOF {
		fmt.Println(err)
		return false, err
	}

	return false, nil
}
<|endoftext|>