<commit_before>package api

import (
	"github.com/go-macaron/binding"
	"github.com/grafana/grafana/pkg/api/avatar"
	"github.com/grafana/grafana/pkg/api/dtos"
	"github.com/grafana/grafana/pkg/api/live"
	"github.com/grafana/grafana/pkg/middleware"
	m "github.com/grafana/grafana/pkg/models"
	"gopkg.in/macaron.v1"
)

// Register adds http routes
func Register(r *macaron.Macaron) {
	reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
	reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
	reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
	reqOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
	quota := middleware.Quota
	bind := binding.Bind

	// not logged in views
	r.Get("/", reqSignedIn, Index)
	r.Get("/logout", Logout)
	r.Post("/login", quota("session"), bind(dtos.LoginCommand{}), wrap(LoginPost))
	r.Get("/login/:name", quota("session"), OAuthLogin)
	r.Get("/login", LoginView)
	r.Get("/invite/:code", Index)

	// authed views
	r.Get("/profile/", reqSignedIn, Index)
	r.Get("/profile/password", reqSignedIn, Index)
	r.Get("/profile/switch-org/:id", reqSignedIn, ChangeActiveOrgAndRedirectToHome)
	r.Get("/org/", reqSignedIn, Index)
	r.Get("/org/new", reqSignedIn, Index)
	r.Get("/datasources/", reqSignedIn, Index)
	r.Get("/datasources/edit/*", reqSignedIn, Index)
	r.Get("/org/users/", reqSignedIn, Index)
	r.Get("/org/apikeys/", reqSignedIn, Index)
	r.Get("/dashboard/import/", reqSignedIn, Index)
	r.Get("/admin", reqGrafanaAdmin, Index)
	r.Get("/admin/settings", reqGrafanaAdmin, Index)
	r.Get("/admin/users", reqGrafanaAdmin, Index)
	r.Get("/admin/users/create", reqGrafanaAdmin, Index)
	r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index)
	r.Get("/admin/orgs", reqGrafanaAdmin, Index)
	r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
	r.Get("/admin/stats", reqGrafanaAdmin, Index)

	r.Get("/styleguide", reqSignedIn, Index)

	r.Get("/plugins", reqSignedIn, Index)
	r.Get("/plugins/:id/edit", reqSignedIn, Index)
	r.Get("/plugins/:id/page/:page", reqSignedIn, Index)

	r.Get("/dashboard/*", reqSignedIn, Index)
	r.Get("/dashboard-solo/*", reqSignedIn, Index)

	r.Get("/playlists/", reqSignedIn, Index)
	r.Get("/playlists/*", reqSignedIn, Index)
	r.Get("/alerts/", reqSignedIn, Index)
	r.Get("/alerts/*", reqSignedIn, Index)

	// sign up
	r.Get("/signup", Index)
	r.Get("/api/user/signup/options", wrap(GetSignUpOptions))
	r.Post("/api/user/signup", quota("user"), bind(dtos.SignUpForm{}), wrap(SignUp))
	r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), wrap(SignUpStep2))

	// invited
	r.Get("/api/user/invite/:code", wrap(GetInviteInfoByCode))
	r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), wrap(CompleteInvite))

	// reset password
	r.Get("/user/password/send-reset-email", Index)
	r.Get("/user/password/reset", Index)

	r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), wrap(SendResetPasswordEmail))
	r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), wrap(ResetPassword))

	// dashboard snapshots
	r.Get("/dashboard/snapshot/*", Index)
	r.Get("/dashboard/snapshots/", reqSignedIn, Index)

	// api for dashboard snapshots
	r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)
	r.Get("/api/snapshot/shared-options/", GetSharingOptions)
	r.Get("/api/snapshots/:key", GetDashboardSnapshot)
	r.Get("/api/snapshots-delete/:key", reqEditorRole, DeleteDashboardSnapshot)

	// api renew session based on remember cookie
	r.Get("/api/login/ping", quota("session"), LoginApiPing)

	// authed api
	r.Group("/api", func() {

		// user (signed in)
		r.Group("/user", func() {
			r.Get("/", wrap(GetSignedInUser))
			r.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser))
			r.Post("/using/:id", wrap(UserSetUsingOrg))
			r.Get("/orgs", wrap(GetSignedInUserOrgList))

			r.Post("/stars/dashboard/:id", wrap(StarDashboard))
			r.Delete("/stars/dashboard/:id", wrap(UnstarDashboard))

			r.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword))
			r.Get("/quotas", wrap(GetUserQuotas))

			r.Get("/preferences", wrap(GetUserPreferences))
			r.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateUserPreferences))
		})

		// users (admin permission required)
		r.Group("/users", func() {
			r.Get("/", wrap(SearchUsers))
			r.Get("/:id", wrap(GetUserById))
			r.Get("/:id/orgs", wrap(GetUserOrgList))
			r.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser))
		}, reqGrafanaAdmin)

		// org information available to all users.
		r.Group("/org", func() {
			r.Get("/", wrap(GetOrgCurrent))
			r.Get("/quotas", wrap(GetOrgQuotas))
		})

		// current org
		r.Group("/org", func() {
			r.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrgCurrent))
			r.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddressCurrent))
			r.Post("/users", quota("user"), bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg))
			r.Get("/users", wrap(GetOrgUsersForCurrentOrg))
			r.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUserForCurrentOrg))
			r.Delete("/users/:userId", wrap(RemoveOrgUserForCurrentOrg))

			// invites
			r.Get("/invites", wrap(GetPendingOrgInvites))
			r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
			r.Patch("/invites/:code/revoke", wrap(RevokeInvite))

			// prefs
			r.Get("/preferences", wrap(GetOrgPreferences))
			r.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateOrgPreferences))
		}, reqOrgAdmin)

		// create new org
		r.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), wrap(CreateOrg))

		// search all orgs
		r.Get("/orgs", reqGrafanaAdmin, wrap(SearchOrgs))

		// orgs (admin routes)
		r.Group("/orgs/:orgId", func() {
			r.Get("/", wrap(GetOrgById))
			r.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrg))
			r.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddress))
			r.Delete("/", wrap(DeleteOrgById))
			r.Get("/users", wrap(GetOrgUsers))
			r.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser))
			r.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser))
			r.Delete("/users/:userId", wrap(RemoveOrgUser))
			r.Get("/quotas", wrap(GetOrgQuotas))
			r.Put("/quotas/:target", bind(m.UpdateOrgQuotaCmd{}), wrap(UpdateOrgQuota))
		}, reqGrafanaAdmin)

		// orgs (admin routes)
		r.Group("/orgs/name/:name", func() {
			r.Get("/", wrap(GetOrgByName))
		}, reqGrafanaAdmin)

		// auth api keys
		r.Group("/auth/keys", func() {
			r.Get("/", wrap(GetApiKeys))
			r.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddApiKey))
			r.Delete("/:id", wrap(DeleteApiKey))
		}, reqOrgAdmin)

		// Preferences
		r.Group("/preferences", func() {
			r.Post("/set-home-dash", bind(m.SavePreferencesCommand{}), wrap(SetHomeDashboard))
		})

		// Data sources
		r.Group("/datasources", func() {
			r.Get("/", GetDataSources)
			r.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), AddDataSource)
			r.Put("/:id", bind(m.UpdateDataSourceCommand{}), UpdateDataSource)
			r.Delete("/:id", DeleteDataSource)
			r.Get("/:id", wrap(GetDataSourceById))
			r.Get("/name/:name", wrap(GetDataSourceByName))
		}, reqOrgAdmin)

		r.Get("/datasources/id/:name", wrap(GetDataSourceIdByName), reqSignedIn)

		r.Get("/plugins", wrap(GetPluginList))
		r.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))

		r.Group("/plugins", func() {
			r.Get("/:pluginId/readme", wrap(GetPluginReadme))
			r.Get("/:pluginId/dashboards/", wrap(GetPluginDashboards))
			r.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
		}, reqOrgAdmin)

		r.Get("/frontend/settings/", GetFrontendSettings)
		r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
		r.Any("/datasources/proxy/:id", reqSignedIn, ProxyDataSourceRequest)

		// Dashboard
		r.Group("/dashboards", func() {
			r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
			r.Post("/db", reqEditorRole, bind(m.SaveDashboardCommand{}), PostDashboard)
			r.Get("/file/:file", GetDashboardFromJsonFile)
			r.Get("/home", GetHomeDashboard)
			r.Get("/tags", GetDashboardTags)
			r.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
		})

		// Dashboard snapshots
		r.Group("/dashboard/snapshots", func() {
			r.Get("/", wrap(SearchDashboardSnapshots))
		})

		// Playlist
		r.Group("/playlists", func() {
			r.Get("/", wrap(SearchPlaylists))
			r.Get("/:id", ValidateOrgPlaylist, wrap(GetPlaylist))
			r.Get("/:id/items", ValidateOrgPlaylist, wrap(GetPlaylistItems))
			r.Get("/:id/dashboards", ValidateOrgPlaylist, wrap(GetPlaylistDashboards))
			r.Delete("/:id", reqEditorRole, ValidateOrgPlaylist, wrap(DeletePlaylist))
			r.Put("/:id", reqEditorRole, bind(m.UpdatePlaylistCommand{}), ValidateOrgPlaylist, wrap(UpdatePlaylist))
			r.Post("/", reqEditorRole, bind(m.CreatePlaylistCommand{}), wrap(CreatePlaylist))
		})

		// Search
		r.Get("/search/", Search)

		// metrics
		r.Get("/metrics/test", GetTestMetrics)

		r.Group("/alerts", func() {
			r.Group("/rules", func() {
				r.Get("/:alertId/states", wrap(GetAlertStates))

				r.Put("/:alertId/state", bind(m.UpdateAlertStateCommand{}), wrap(PutAlertState))

				r.Get("/:alertId", ValidateOrgAlert, wrap(GetAlert))
				r.Delete("/:alertId", ValidateOrgAlert, wrap(DelAlert))
				r.Get("/", wrap(GetAlerts))
			})

			r.Get("/changes", wrap(GetAlertChanges))
		})

	}, reqSignedIn)

	// admin api
	r.Group("/api/admin", func() {
		r.Get("/settings", AdminGetSettings)
		r.Post("/users", bind(dtos.AdminCreateUserForm{}), AdminCreateUser)
		r.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword)
		r.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions)
		r.Delete("/users/:id", AdminDeleteUser)
		r.Get("/users/:id/quotas", wrap(GetUserQuotas))
		r.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), wrap(UpdateUserQuota))
		r.Get("/stats", AdminGetStats)
	}, reqGrafanaAdmin)

	// rendering
	r.Get("/render/*", reqSignedIn, RenderToPng)

	// grafana.net proxy
	r.Any("/api/gnet/*", reqSignedIn, ProxyGnetRequest)

	// Gravatar service.
	avt := avatar.CacheServer()
	r.Get("/avatar/:hash", avt.ServeHTTP)

	// Websocket
	liveConn := live.New()
	r.Any("/ws", liveConn.Serve)

	// streams
	r.Post("/api/streams/push", reqSignedIn, bind(dtos.StreamMessage{}), liveConn.PushToStream)

	InitAppPluginRoutes(r)

}
<commit_msg>tech(alerting): disable DEL for alert rules in the api<commit_after>package api

import (
	"github.com/go-macaron/binding"
	"github.com/grafana/grafana/pkg/api/avatar"
	"github.com/grafana/grafana/pkg/api/dtos"
	"github.com/grafana/grafana/pkg/api/live"
	"github.com/grafana/grafana/pkg/middleware"
	m "github.com/grafana/grafana/pkg/models"
	"gopkg.in/macaron.v1"
)

// Register adds http routes
func Register(r *macaron.Macaron) {
	reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
	reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
	reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
	reqOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
	quota := middleware.Quota
	bind := binding.Bind

	// not logged in views
	r.Get("/", reqSignedIn, Index)
	r.Get("/logout", Logout)
	r.Post("/login", quota("session"), bind(dtos.LoginCommand{}), wrap(LoginPost))
	r.Get("/login/:name", quota("session"), OAuthLogin)
	r.Get("/login", LoginView)
	r.Get("/invite/:code", Index)

	// authed views
	r.Get("/profile/", reqSignedIn, Index)
	r.Get("/profile/password", reqSignedIn, Index)
	r.Get("/profile/switch-org/:id", reqSignedIn, ChangeActiveOrgAndRedirectToHome)
	r.Get("/org/", reqSignedIn, Index)
	r.Get("/org/new", reqSignedIn, Index)
	r.Get("/datasources/", reqSignedIn, Index)
	r.Get("/datasources/edit/*", reqSignedIn, Index)
	r.Get("/org/users/", reqSignedIn, Index)
	r.Get("/org/apikeys/", reqSignedIn, Index)
	r.Get("/dashboard/import/", reqSignedIn, Index)
	r.Get("/admin", reqGrafanaAdmin, Index)
	r.Get("/admin/settings", reqGrafanaAdmin, Index)
	r.Get("/admin/users", reqGrafanaAdmin, Index)
	r.Get("/admin/users/create", reqGrafanaAdmin, Index)
	r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index)
	r.Get("/admin/orgs", reqGrafanaAdmin, Index)
	r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index)
	r.Get("/admin/stats", reqGrafanaAdmin, Index)

	r.Get("/styleguide", reqSignedIn, Index)

	r.Get("/plugins", reqSignedIn, Index)
	r.Get("/plugins/:id/edit", reqSignedIn, Index)
	r.Get("/plugins/:id/page/:page", reqSignedIn, Index)

	r.Get("/dashboard/*", reqSignedIn, Index)
	r.Get("/dashboard-solo/*", reqSignedIn, Index)

	r.Get("/playlists/", reqSignedIn, Index)
	r.Get("/playlists/*", reqSignedIn, Index)
	r.Get("/alerts/", reqSignedIn, Index)
	r.Get("/alerts/*", reqSignedIn, Index)

	// sign up
	r.Get("/signup", Index)
	r.Get("/api/user/signup/options", wrap(GetSignUpOptions))
	r.Post("/api/user/signup", quota("user"), bind(dtos.SignUpForm{}), wrap(SignUp))
	r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), wrap(SignUpStep2))

	// invited
	r.Get("/api/user/invite/:code", wrap(GetInviteInfoByCode))
	r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), wrap(CompleteInvite))

	// reset password
	r.Get("/user/password/send-reset-email", Index)
	r.Get("/user/password/reset", Index)

	r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), wrap(SendResetPasswordEmail))
	r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), wrap(ResetPassword))

	// dashboard snapshots
	r.Get("/dashboard/snapshot/*", Index)
	r.Get("/dashboard/snapshots/", reqSignedIn, Index)

	// api for dashboard snapshots
	r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)
	r.Get("/api/snapshot/shared-options/", GetSharingOptions)
	r.Get("/api/snapshots/:key", GetDashboardSnapshot)
	r.Get("/api/snapshots-delete/:key", reqEditorRole, DeleteDashboardSnapshot)

	// api renew session based on remember cookie
	r.Get("/api/login/ping", quota("session"), LoginApiPing)

	// authed api
	r.Group("/api", func() {

		// user (signed in)
		r.Group("/user", func() {
			r.Get("/", wrap(GetSignedInUser))
			r.Put("/", bind(m.UpdateUserCommand{}), wrap(UpdateSignedInUser))
			r.Post("/using/:id", wrap(UserSetUsingOrg))
			r.Get("/orgs", wrap(GetSignedInUserOrgList))

			r.Post("/stars/dashboard/:id", wrap(StarDashboard))
			r.Delete("/stars/dashboard/:id", wrap(UnstarDashboard))

			r.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword))
			r.Get("/quotas", wrap(GetUserQuotas))

			r.Get("/preferences", wrap(GetUserPreferences))
			r.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateUserPreferences))
		})

		// users (admin permission required)
		r.Group("/users", func() {
			r.Get("/", wrap(SearchUsers))
			r.Get("/:id", wrap(GetUserById))
			r.Get("/:id/orgs", wrap(GetUserOrgList))
			r.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser))
		}, reqGrafanaAdmin)

		// org information available to all users.
		r.Group("/org", func() {
			r.Get("/", wrap(GetOrgCurrent))
			r.Get("/quotas", wrap(GetOrgQuotas))
		})

		// current org
		r.Group("/org", func() {
			r.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrgCurrent))
			r.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddressCurrent))
			r.Post("/users", quota("user"), bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg))
			r.Get("/users", wrap(GetOrgUsersForCurrentOrg))
			r.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUserForCurrentOrg))
			r.Delete("/users/:userId", wrap(RemoveOrgUserForCurrentOrg))

			// invites
			r.Get("/invites", wrap(GetPendingOrgInvites))
			r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite))
			r.Patch("/invites/:code/revoke", wrap(RevokeInvite))

			// prefs
			r.Get("/preferences", wrap(GetOrgPreferences))
			r.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateOrgPreferences))
		}, reqOrgAdmin)

		// create new org
		r.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), wrap(CreateOrg))

		// search all orgs
		r.Get("/orgs", reqGrafanaAdmin, wrap(SearchOrgs))

		// orgs (admin routes)
		r.Group("/orgs/:orgId", func() {
			r.Get("/", wrap(GetOrgById))
			r.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrg))
			r.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddress))
			r.Delete("/", wrap(DeleteOrgById))
			r.Get("/users", wrap(GetOrgUsers))
			r.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser))
			r.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser))
			r.Delete("/users/:userId", wrap(RemoveOrgUser))
			r.Get("/quotas", wrap(GetOrgQuotas))
			r.Put("/quotas/:target", bind(m.UpdateOrgQuotaCmd{}), wrap(UpdateOrgQuota))
		}, reqGrafanaAdmin)

		// orgs (admin routes)
		r.Group("/orgs/name/:name", func() {
			r.Get("/", wrap(GetOrgByName))
		}, reqGrafanaAdmin)

		// auth api keys
		r.Group("/auth/keys", func() {
			r.Get("/", wrap(GetApiKeys))
			r.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddApiKey))
			r.Delete("/:id", wrap(DeleteApiKey))
		}, reqOrgAdmin)

		// Preferences
		r.Group("/preferences", func() {
			r.Post("/set-home-dash", bind(m.SavePreferencesCommand{}), wrap(SetHomeDashboard))
		})

		// Data sources
		r.Group("/datasources", func() {
			r.Get("/", GetDataSources)
			r.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), AddDataSource)
			r.Put("/:id", bind(m.UpdateDataSourceCommand{}), UpdateDataSource)
			r.Delete("/:id", DeleteDataSource)
			r.Get("/:id", wrap(GetDataSourceById))
			r.Get("/name/:name", wrap(GetDataSourceByName))
		}, reqOrgAdmin)

		r.Get("/datasources/id/:name", wrap(GetDataSourceIdByName), reqSignedIn)

		r.Get("/plugins", wrap(GetPluginList))
		r.Get("/plugins/:pluginId/settings", wrap(GetPluginSettingById))

		r.Group("/plugins", func() {
			r.Get("/:pluginId/readme", wrap(GetPluginReadme))
			r.Get("/:pluginId/dashboards/", wrap(GetPluginDashboards))
			r.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))
		}, reqOrgAdmin)

		r.Get("/frontend/settings/", GetFrontendSettings)
		r.Any("/datasources/proxy/:id/*", reqSignedIn, ProxyDataSourceRequest)
		r.Any("/datasources/proxy/:id", reqSignedIn, ProxyDataSourceRequest)

		// Dashboard
		r.Group("/dashboards", func() {
			r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
			r.Post("/db", reqEditorRole, bind(m.SaveDashboardCommand{}), PostDashboard)
			r.Get("/file/:file", GetDashboardFromJsonFile)
			r.Get("/home", GetHomeDashboard)
			r.Get("/tags", GetDashboardTags)
			r.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
		})

		// Dashboard snapshots
		r.Group("/dashboard/snapshots", func() {
			r.Get("/", wrap(SearchDashboardSnapshots))
		})

		// Playlist
		r.Group("/playlists", func() {
			r.Get("/", wrap(SearchPlaylists))
			r.Get("/:id", ValidateOrgPlaylist, wrap(GetPlaylist))
			r.Get("/:id/items", ValidateOrgPlaylist, wrap(GetPlaylistItems))
			r.Get("/:id/dashboards", ValidateOrgPlaylist, wrap(GetPlaylistDashboards))
			r.Delete("/:id", reqEditorRole, ValidateOrgPlaylist, wrap(DeletePlaylist))
			r.Put("/:id", reqEditorRole, bind(m.UpdatePlaylistCommand{}), ValidateOrgPlaylist, wrap(UpdatePlaylist))
			r.Post("/", reqEditorRole, bind(m.CreatePlaylistCommand{}), wrap(CreatePlaylist))
		})

		// Search
		r.Get("/search/", Search)

		// metrics
		r.Get("/metrics/test", GetTestMetrics)

		r.Group("/alerts", func() {
			r.Group("/rules", func() {
				r.Get("/:alertId/states", wrap(GetAlertStates))

				r.Put("/:alertId/state", bind(m.UpdateAlertStateCommand{}), wrap(PutAlertState))

				r.Get("/:alertId", ValidateOrgAlert, wrap(GetAlert))
				//r.Delete("/:alertId", ValidateOrgAlert, wrap(DelAlert)) disabled until we know how to handle it dashboard updates
				r.Get("/", wrap(GetAlerts))
			})

			r.Get("/changes", wrap(GetAlertChanges))
		})

	}, reqSignedIn)

	// admin api
	r.Group("/api/admin", func() {
		r.Get("/settings", AdminGetSettings)
		r.Post("/users", bind(dtos.AdminCreateUserForm{}), AdminCreateUser)
		r.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword)
		r.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions)
		r.Delete("/users/:id", AdminDeleteUser)
		r.Get("/users/:id/quotas", wrap(GetUserQuotas))
		r.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), wrap(UpdateUserQuota))
		r.Get("/stats", AdminGetStats)
	}, reqGrafanaAdmin)

	// rendering
	r.Get("/render/*", reqSignedIn, RenderToPng)

	// grafana.net proxy
	r.Any("/api/gnet/*", reqSignedIn, ProxyGnetRequest)

	// Gravatar service.
	avt := avatar.CacheServer()
	r.Get("/avatar/:hash", avt.ServeHTTP)

	// Websocket
	liveConn := live.New()
	r.Any("/ws", liveConn.Serve)

	// streams
	r.Post("/api/streams/push", reqSignedIn, bind(dtos.StreamMessage{}), liveConn.PushToStream)

	InitAppPluginRoutes(r)

}
<|endoftext|>


<commit_before>package app

import (
	"context"
	"errors"
	"log"
	"sync"
	"time"

	"github.com/atlassian/smith"
	"github.com/atlassian/smith/pkg/processor"
	"github.com/atlassian/smith/pkg/readychecker"
	"github.com/atlassian/smith/pkg/resources"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	apiv1 "k8s.io/client-go/pkg/api/v1"
	appsv1beta1 "k8s.io/client-go/pkg/apis/apps/v1beta1"
	extensions "k8s.io/client-go/pkg/apis/extensions/v1beta1"
	settings "k8s.io/client-go/pkg/apis/settings/v1alpha1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/cache"
)

type App struct {
	RestConfig       *rest.Config
	ResyncPeriod     time.Duration
	DisablePodPreset bool
}

func (a *App) Run(ctx context.Context) error {
	clientset, err := kubernetes.NewForConfig(a.RestConfig)
	if err != nil {
		return err
	}
	scheme := runtime.NewScheme()
	scheme.AddUnversionedTypes(apiv1.SchemeGroupVersion, &metav1.Status{})
	smith.AddToScheme(scheme)

	bundleClient, err := resources.GetBundleTprClient(a.RestConfig, scheme)
	if err != nil {
		return err
	}

	clients := dynamic.NewClientPool(a.RestConfig, nil, dynamic.LegacyAPIPathResolverFunc)

	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	// 1. Informers

	informerFactory := informers.NewSharedInformerFactory(clientset, a.ResyncPeriod)
	tprInf := informerFactory.Extensions().V1beta1().ThirdPartyResources().Informer()
	deploymentExtInf := informerFactory.Extensions().V1beta1().Deployments().Informer()
	ingressInf := informerFactory.Extensions().V1beta1().Ingresses().Informer()
	serviceInf := informerFactory.Core().V1().Services().Informer()
	configMapInf := informerFactory.Core().V1().ConfigMaps().Informer()
	secretInf := informerFactory.Core().V1().Secrets().Informer()
	deploymentAppsInf := informerFactory.Apps().V1beta1().Deployments().Informer()
	var podPresetInf cache.SharedIndexInformer
	if !a.DisablePodPreset {
		podPresetInf = informerFactory.Settings().V1alpha1().PodPresets().Informer()
	}
	bundleInf := a.bundleInformer(bundleClient)

	// 1.5 Store
	store := resources.NewStore(scheme.DeepCopy)

	var wgStore sync.WaitGroup
	defer wgStore.Wait() // await store termination

	ctxStore, cancelStore := context.WithCancel(context.Background())
	defer cancelStore() // signal store to stop
	wgStore.Add(1)
	go store.Run(ctxStore, wgStore.Done)

	store.AddInformer(smith.TprGVK, tprInf)
	store.AddInformer(extensions.SchemeGroupVersion.WithKind("Deployment"), deploymentExtInf)
	store.AddInformer(extensions.SchemeGroupVersion.WithKind("Ingress"), ingressInf)
	store.AddInformer(apiv1.SchemeGroupVersion.WithKind("Service"), serviceInf)
	store.AddInformer(apiv1.SchemeGroupVersion.WithKind("ConfigMap"), configMapInf)
	store.AddInformer(apiv1.SchemeGroupVersion.WithKind("Secret"), secretInf)
	store.AddInformer(appsv1beta1.SchemeGroupVersion.WithKind("Deployment"), deploymentAppsInf)
	if !a.DisablePodPreset {
		store.AddInformer(settings.SchemeGroupVersion.WithKind("PodPreset"), podPresetInf)
	}
	store.AddInformer(smith.BundleGVK, bundleInf)

	informerFactory.Start(ctx.Done()) // Must be after store.AddInformer()

	// We must wait for tprInf to populate its cache to avoid reading from an empty cache
	// in Ready Checker.
	if !cache.WaitForCacheSync(ctx.Done(), tprInf.HasSynced) {
		return errors.New("wait for TPR Informer was cancelled")
	}

	// 2. Ready Checker

	rc := &readychecker.ReadyChecker{
		Store: &tprStore{
			store: store,
		},
	}

	// 3. Processor

	bp := processor.New(bundleClient, clients, rc, scheme.DeepCopy, store)
	var wg sync.WaitGroup
	defer wg.Wait() // await termination
	wg.Add(1)
	go bp.Run(ctx, wg.Done)
	defer cancel() // cancel ctx to signal done to processor (and everything else)

	// 4. Ensure ThirdPartyResource TEMPLATE exists
	bundleTpr := &extensions.ThirdPartyResource{
		ObjectMeta: metav1.ObjectMeta{
			Name: smith.BundleResourceName,
		},
		Description: "Smith resource manager",
		Versions: []extensions.APIVersion{
			{Name: smith.BundleResourceVersion},
		},
	}
	err = retryUntilSuccessOrDone(ctx, func() error {
		return resources.EnsureTprExists(ctx, clientset, store, bundleTpr)
	}, func(e error) bool {
		// TODO be smarter about what is retried
		log.Printf("Failed to create resource %s: %v", smith.BundleResourceName, e)
		return false
	})
	if err != nil {
		return err
	}

	// 5. Watch Bundles

	bundleInf.AddEventHandler(&bundleEventHandler{
		ctx:       ctx,
		processor: bp,
		deepCopy:  scheme.DeepCopy,
	})

	go bundleInf.Run(ctx.Done())

	// We must wait for bundleInf to populate its cache to avoid reading from an empty cache
	// in case of resource-generated events.
	if !cache.WaitForCacheSync(ctx.Done(), bundleInf.HasSynced) {
		return errors.New("wait for Bundle Informer was cancelled")
	}

	bs := &bundleStore{
		store:         store,
		bundleByIndex: bundleInf.GetIndexer().ByIndex,
		deepCopy:      scheme.DeepCopy,
	}
	reh := &resourceEventHandler{
		ctx:         ctx,
		processor:   bp,
		name2bundle: bs.Get,
	}

	// 6. Watch supported built-in resource types

	deploymentExtInf.AddEventHandler(reh)
	ingressInf.AddEventHandler(reh)
	serviceInf.AddEventHandler(reh)
	configMapInf.AddEventHandler(reh)
	secretInf.AddEventHandler(reh)
	deploymentAppsInf.AddEventHandler(reh)
	if !a.DisablePodPreset {
		podPresetInf.AddEventHandler(reh)
	}

	// 7. Watch Third Party Resources to add watches for supported ones

	tprInf.AddEventHandler(newTprEventHandler(ctx, reh, clients, store, bp, bs, a.ResyncPeriod))

	<-ctx.Done()
	return ctx.Err()
}

func (a *App) bundleInformer(bundleClient cache.Getter) cache.SharedIndexInformer {
	return cache.NewSharedIndexInformer(
		cache.NewListWatchFromClient(bundleClient, smith.BundleResourcePath, metav1.NamespaceAll, fields.Everything()),
		&smith.Bundle{},
		a.ResyncPeriod,
		cache.Indexers{
			ByTprNameIndex: byTprNameIndex,
		})
}
<commit_msg>Refactor informers creation<commit_after>package app

import (
	"context"
	"errors"
	"log"
	"sync"
	"time"

	"github.com/atlassian/smith"
	"github.com/atlassian/smith/pkg/processor"
	"github.com/atlassian/smith/pkg/readychecker"
	"github.com/atlassian/smith/pkg/resources"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	apiv1 "k8s.io/client-go/pkg/api/v1"
	appsv1beta1 "k8s.io/client-go/pkg/apis/apps/v1beta1"
	extensions "k8s.io/client-go/pkg/apis/extensions/v1beta1"
	settings "k8s.io/client-go/pkg/apis/settings/v1alpha1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/cache"
)

type App struct {
	RestConfig       *rest.Config
	ResyncPeriod     time.Duration
	DisablePodPreset bool
}

func (a *App) Run(ctx context.Context) error {
	clientset, err := kubernetes.NewForConfig(a.RestConfig)
	if err != nil {
		return err
	}
	scheme := runtime.NewScheme()
	scheme.AddUnversionedTypes(apiv1.SchemeGroupVersion, &metav1.Status{})
	smith.AddToScheme(scheme)

	bundleClient, err := resources.GetBundleTprClient(a.RestConfig, scheme)
	if err != nil {
		return err
	}

	clients := dynamic.NewClientPool(a.RestConfig, nil, dynamic.LegacyAPIPathResolverFunc)

	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	// 1. Store
	store := resources.NewStore(scheme.DeepCopy)

	var wgStore sync.WaitGroup
	defer wgStore.Wait() // await store termination

	ctxStore, cancelStore := context.WithCancel(context.Background())
	defer cancelStore() // signal store to stop
	wgStore.Add(1)
	go store.Run(ctxStore, wgStore.Done)

	// 1.5. Informers
	bundleInf := a.bundleInformer(bundleClient)
	store.AddInformer(smith.BundleGVK, bundleInf)

	infs, err := a.resourceInformers(ctx, store, clientset)
	if err != nil {
		return err
	}
	tprInf := infs[smith.TprGVK]
	delete(infs, smith.TprGVK) // To avoid adding generic event handler later

	// We must wait for tprInf to populate its cache to avoid reading from an empty cache
	// in Ready Checker.
	if !cache.WaitForCacheSync(ctx.Done(), tprInf.HasSynced) {
		return errors.New("wait for TPR Informer was cancelled")
	}

	// 2. Ready Checker

	rc := &readychecker.ReadyChecker{
		Store: &tprStore{
			store: store,
		},
	}

	// 3. Processor

	bp := processor.New(bundleClient, clients, rc, scheme.DeepCopy, store)
	var wg sync.WaitGroup
	defer wg.Wait() // await termination
	wg.Add(1)
	go bp.Run(ctx, wg.Done)
	defer cancel() // cancel ctx to signal done to processor (and everything else)

	// 4. Ensure ThirdPartyResource TEMPLATE exists
	bundleTpr := &extensions.ThirdPartyResource{
		ObjectMeta: metav1.ObjectMeta{
			Name: smith.BundleResourceName,
		},
		Description: "Smith resource manager",
		Versions: []extensions.APIVersion{
			{Name: smith.BundleResourceVersion},
		},
	}
	err = retryUntilSuccessOrDone(ctx, func() error {
		return resources.EnsureTprExists(ctx, clientset, store, bundleTpr)
	}, func(e error) bool {
		// TODO be smarter about what is retried
		log.Printf("Failed to create resource %s: %v", smith.BundleResourceName, e)
		return false
	})
	if err != nil {
		return err
	}

	// 5. Watch Bundles

	bundleInf.AddEventHandler(&bundleEventHandler{
		ctx:       ctx,
		processor: bp,
		deepCopy:  scheme.DeepCopy,
	})

	go bundleInf.Run(ctx.Done())

	// We must wait for bundleInf to populate its cache to avoid reading from an empty cache
	// in case of resource-generated events.
	if !cache.WaitForCacheSync(ctx.Done(), bundleInf.HasSynced) {
		return errors.New("wait for Bundle Informer was cancelled")
	}

	bs := &bundleStore{
		store:         store,
		bundleByIndex: bundleInf.GetIndexer().ByIndex,
		deepCopy:      scheme.DeepCopy,
	}
	reh := &resourceEventHandler{
		ctx:         ctx,
		processor:   bp,
		name2bundle: bs.Get,
	}

	// 6. Watch supported built-in resource types

	for _, inf := range infs {
		inf.AddEventHandler(reh)
	}

	// 7. Watch Third Party Resources to add watches for supported ones

	tprInf.AddEventHandler(newTprEventHandler(ctx, reh, clients, store, bp, bs, a.ResyncPeriod))

	<-ctx.Done()
	return ctx.Err()
}

func (a *App) bundleInformer(bundleClient cache.Getter) cache.SharedIndexInformer {
	return cache.NewSharedIndexInformer(
		cache.NewListWatchFromClient(bundleClient, smith.BundleResourcePath, metav1.NamespaceAll, fields.Everything()),
		&smith.Bundle{},
		a.ResyncPeriod,
		cache.Indexers{
			ByTprNameIndex: byTprNameIndex,
		})
}

func (a *App) resourceInformers(ctx context.Context, store *resources.Store, mainClient kubernetes.Interface) (map[schema.GroupVersionKind]cache.SharedIndexInformer, error) {
	mainSif := informers.NewSharedInformerFactory(mainClient, a.ResyncPeriod)

	// Core API types
	infs := map[schema.GroupVersionKind]cache.SharedIndexInformer{
		smith.TprGVK: mainSif.Extensions().V1beta1().ThirdPartyResources().Informer(),
		extensions.SchemeGroupVersion.WithKind("Deployment"):  mainSif.Extensions().V1beta1().Deployments().Informer(),
		extensions.SchemeGroupVersion.WithKind("Ingress"):     mainSif.Extensions().V1beta1().Ingresses().Informer(),
		apiv1.SchemeGroupVersion.WithKind("Service"):          mainSif.Core().V1().Services().Informer(),
		apiv1.SchemeGroupVersion.WithKind("ConfigMap"):        mainSif.Core().V1().ConfigMaps().Informer(),
		apiv1.SchemeGroupVersion.WithKind("Secret"):           mainSif.Core().V1().Secrets().Informer(),
		appsv1beta1.SchemeGroupVersion.WithKind("Deployment"): mainSif.Apps().V1beta1().Deployments().Informer(),
	}
	if !a.DisablePodPreset {
		infs[settings.SchemeGroupVersion.WithKind("PodPreset")] = mainSif.Settings().V1alpha1().PodPresets().Informer()
	}

	// Add all to Store
	for gvk, inf := range infs {
		store.AddInformer(gvk, inf)
	}

	// Start informers
	mainSif.Start(ctx.Done()) // Must be after store.AddInformer()
	return infs, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 mutable

import (
	"errors"
	"fmt"
	"io"
	"os"
	"time"

	"github.com/jacobsa/timeutil"
	"golang.org/x/net/context"
)

// A temporary file that keeps track of the lowest offset at which it has been
// modified.
type TempFile interface {
	// Panic if any internal invariants are violated.
	CheckInvariants()

	// Semantics matching os.File.
	io.ReadSeeker
	io.ReaderAt
	io.WriterAt
	Truncate(n int64) (err error)

	// Return information about the current state of the content.
	Stat() (sr StatResult, err error)

	// Throw away the resources used by the temporary file. The object must not
	// be used again.
	Destroy()
}

type StatResult struct {
	// The current size in bytes of the content.
	Size int64

	// It is guaranteed that all bytes in the range [0, DirtyThreshold) are
	// unmodified from the original content with which the mutable content object
	// was created.
	DirtyThreshold int64

	// The time at which the content was last updated, or nil if we've never
	// changed it.
	Mtime *time.Time
}

// Create a temp file whose initial contents are given by the supplied reader.
func NewTempFile(
	content io.Reader,
	clock timeutil.Clock) (tf TempFile, err error) {
	err = errors.New("TODO")
	return
}

type mutableContent struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	clock timeutil.Clock

	/////////////////////////
	// Mutable state
	/////////////////////////

	destroyed bool

	// Have we been dirtied since we were created?
	dirty bool

	// A file containing our current contents.
	contents *os.File

	// The lowest byte index that has been modified from the initial contents.
	//
	// INVARIANT: !dirty => Stat().DirtyThreshold == Stat().Size
	dirtyThreshold int64

	// The time at which a method that modifies our contents was last called, or
	// nil if never.
	//
	// INVARIANT: dirty => mtime != nil
	mtime *time.Time
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (mc *mutableContent) CheckInvariants() {
	if mc.destroyed {
		panic("Use of destroyed mutableContent object.")
	}

	// INVARIANT: !dirty => Stat().DirtyThreshold == Stat().Size
	if !mc.dirty {
		sr, err := mc.Stat(context.Background())
		if err != nil {
			panic(fmt.Sprintf("Stat: %v", err))
		}

		if sr.DirtyThreshold != sr.Size {
			panic(fmt.Sprintf("Mismatch: %d vs. %d", sr.DirtyThreshold, sr.Size))
		}
	}

	// INVARIANT: dirty => mtime != nil
	if mc.dirty && mc.mtime == nil {
		panic("Expected a non-nil mtime")
	}
}

func (mc *mutableContent) Destroy() {
	mc.destroyed = true

	// Throw away the file.
	mc.contents.Close()
	mc.contents = nil
}

func (mc *mutableContent) ReadAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	n, err = mc.contents.ReadAt(buf, offset)
	return
}

func (mc *mutableContent) Stat(
	ctx context.Context) (sr StatResult, err error) {
	sr.DirtyThreshold = mc.dirtyThreshold
	sr.Mtime = mc.mtime

	// Get the size from the file.
	sr.Size, err = mc.contents.Seek(0, 2)
	if err != nil {
		err = fmt.Errorf("Seek: %v", err)
		return
	}

	return
}

func (mc *mutableContent) WriteAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	// Update our state regarding being dirty.
	mc.dirtyThreshold = minInt64(mc.dirtyThreshold, offset)

	newMtime := mc.clock.Now()
	mc.mtime = &newMtime

	// Call through.
	n, err = mc.contents.WriteAt(buf, offset)

	return
}

func (mc *mutableContent) Truncate(
	ctx context.Context,
	n int64) (err error) {
	// Update our state regarding being dirty.
	mc.dirtyThreshold = minInt64(mc.dirtyThreshold, n)

	newMtime := mc.clock.Now()
	mc.mtime = &newMtime

	// Call through.
	err = mc.contents.Truncate(int64(n))

	return
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func minInt64(a int64, b int64) int64 {
	if a < b {
		return a
	}

	return b
}
<commit_msg>Fixed some build errors.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 mutable

import (
	"errors"
	"fmt"
	"io"
	"os"
	"time"

	"github.com/jacobsa/timeutil"
	"golang.org/x/net/context"
)

// A temporary file that keeps track of the lowest offset at which it has been
// modified.
type TempFile interface {
	// Panic if any internal invariants are violated.
	CheckInvariants()

	// Semantics matching os.File.
	io.ReadSeeker
	io.ReaderAt
	io.WriterAt
	Truncate(n int64) (err error)

	// Return information about the current state of the content.
	Stat() (sr StatResult, err error)

	// Throw away the resources used by the temporary file. The object must not
	// be used again.
	Destroy()
}

type StatResult struct {
	// The current size in bytes of the content.
	Size int64

	// It is guaranteed that all bytes in the range [0, DirtyThreshold) are
	// unmodified from the original content with which the mutable content object
	// was created.
	DirtyThreshold int64

	// The time at which the content was last updated, or nil if we've never
	// changed it.
	Mtime *time.Time
}

// Create a temp file whose initial contents are given by the supplied reader.
func NewTempFile(
	content io.Reader,
	clock timeutil.Clock) (tf TempFile, err error) {
	err = errors.New("TODO")
	return
}

type tempFile struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	clock timeutil.Clock

	/////////////////////////
	// Mutable state
	/////////////////////////

	destroyed bool

	// Have we been dirtied since we were created?
	dirty bool

	// A file containing our current contents.
	f *os.File

	// The lowest byte index that has been modified from the initial contents.
	//
	// INVARIANT: !dirty => Stat().DirtyThreshold == Stat().Size
	dirtyThreshold int64

	// The time at which a method that modifies our contents was last called, or
	// nil if never.
	//
	// INVARIANT: dirty => mtime != nil
	mtime *time.Time
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (mc *tempFile) CheckInvariants() {
	if mc.destroyed {
		panic("Use of destroyed tempFile object.")
	}

	// INVARIANT: !dirty => Stat().DirtyThreshold == Stat().Size
	if !mc.dirty {
		sr, err := mc.Stat(context.Background())
		if err != nil {
			panic(fmt.Sprintf("Stat: %v", err))
		}

		if sr.DirtyThreshold != sr.Size {
			panic(fmt.Sprintf("Mismatch: %d vs. %d", sr.DirtyThreshold, sr.Size))
		}
	}

	// INVARIANT: dirty => mtime != nil
	if mc.dirty && mc.mtime == nil {
		panic("Expected a non-nil mtime")
	}
}

func (mc *tempFile) Destroy() {
	mc.destroyed = true

	// Throw away the file.
	mc.f.Close()
	mc.f = nil
}

func (mc *tempFile) ReadAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	n, err = mc.f.ReadAt(buf, offset)
	return
}

func (mc *tempFile) Stat(
	ctx context.Context) (sr StatResult, err error) {
	sr.DirtyThreshold = mc.dirtyThreshold
	sr.Mtime = mc.mtime

	// Get the size from the file.
	sr.Size, err = mc.f.Seek(0, 2)
	if err != nil {
		err = fmt.Errorf("Seek: %v", err)
		return
	}

	return
}

func (mc *tempFile) WriteAt(
	ctx context.Context,
	buf []byte,
	offset int64) (n int, err error) {
	// Update our state regarding being dirty.
	mc.dirtyThreshold = minInt64(mc.dirtyThreshold, offset)

	newMtime := mc.clock.Now()
	mc.mtime = &newMtime

	// Call through.
	n, err = mc.f.WriteAt(buf, offset)

	return
}

func (mc *tempFile) Truncate(
	ctx context.Context,
	n int64) (err error) {
	// Update our state regarding being dirty.
	mc.dirtyThreshold = minInt64(mc.dirtyThreshold, n)

	newMtime := mc.clock.Now()
	mc.mtime = &newMtime

	// Call through.
	err = mc.f.Truncate(int64(n))

	return
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func minInt64(a int64, b int64) int64 {
	if a < b {
		return a
	}

	return b
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Pani Networks
// All Rights Reserved.
//
// 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 provides tools for romana CNI plugin to interact with other
// Romana services.
package cni

import (
	"fmt"
	"net"

	"github.com/containernetworking/cni/pkg/types"
	"github.com/romana/core/common"
	log "github.com/romana/rlog"
	"github.com/vishvananda/netlink"
)

// RomanaAddresManager describes functions that allow allocating and deallocating
// IP addresses from Romana.
type RomanaAddresManager interface {
	Allocate(NetConf, RomanaAllocatorPodDescription) (*net.IPNet, error)
	Deallocate(NetConf, string) error
}

// NewRomanaAddressManager returns structure that satisfies RomanaAddresManager,
// it allows multiple implementations.
func NewRomanaAddressManager(provider RomanaAddressManagerProvider) (RomanaAddresManager, error) {
	if provider == DefaultProvider {
		return DefaultAddressManager{}, nil
	}

	return nil, fmt.Errorf("Unknown provider type %s", provider)
}

type RomanaAddressManagerProvider string

// DefaultProvider allocates and deallocates IP addresses using rest requests
// to Romana IPAM.
const DefaultProvider RomanaAddressManagerProvider = "default"

// RomanaAllocatorPodDescription represents collection of parameters used to allocate IP address.
type RomanaAllocatorPodDescription struct {
	Name        string
	Hostname    string
	Namespace   string
	Labels      map[string]string
	Annotations map[string]string
}

// NetConf represents parameters CNI plugin receives via stdin.
type NetConf struct {
	FeatureIP6TW
	types.NetConf
	KubernetesConfig string `json:"kubernetes_config"`
	RomanaRoot       string `json:"romana_root"`

	// Name of a current host in romana.
	// If omitted, current hostname will be used.
	RomanaHostName   string `json:"romana_host_name"`
	SegmentLabelName string `json:"segment_label_name"`
	TenantLabelName  string `json:"tenant_label_name"` // TODO for stas, we don't use it. May be it should go away.
	UseAnnotations   bool   `json:"use_annotattions"`
}

// The structure should be in separate file.
type FeatureIP6TW struct {
	// Fields relative to the feature.
}

type DefaultAddressManager struct{}

func (DefaultAddressManager) Allocate(config NetConf, pod RomanaAllocatorPodDescription) (*net.IPNet, error) {
	// Discover pod segment.
	var segmentLabel string
	var ok bool
	if config.UseAnnotations {
		segmentLabel, ok = pod.Annotations[config.SegmentLabelName]
	} else {
		segmentLabel, ok = pod.Labels[config.SegmentLabelName]
	}
	if !ok {
		return nil, fmt.Errorf("Failed to discover segment label for a pod")
	}
	log.Infof("Discovered segment %s for a pod", segmentLabel)

	// Rest client config
	clientConfig := common.GetDefaultRestClientConfig(config.RomanaRoot, nil)
	client, err := common.NewRestClient(clientConfig)
	if err != nil {
		return nil, fmt.Errorf("Failed to reach romana root at %s, err=(%s)", config.RomanaRoot, err)
	}
	log.Infof("Created romana client %v", client)

	// Topology, find host id.
	hosts, err := client.ListHosts()
	if err != nil {
		return nil, fmt.Errorf("Failed to list romana hosts err=(%s)", err)
	}
	var currentHost common.Host
	for hostNum, host := range hosts {
		if host.Name == config.RomanaHostName {
			currentHost = hosts[hostNum]
			break
		}
	}
	if currentHost.Name == "" {
		return nil, fmt.Errorf("Failed to find romana host with name %s in romana database", config.RomanaHostName)
	}

	// Tenant and segemnt
	tenantUrl, err := client.GetServiceUrl("tenant")
	if err != nil {
		return nil, fmt.Errorf("Failed to discover tenant url from romana root err=(%s)", err)
	}
	tenantUrl += "/tenants"
	var tenants []common.Tenant
	err = client.Get(tenantUrl, &tenants)
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch romana tenants from %s, err=(%s)", tenantUrl, err)
	}
	var currentTenant common.Tenant
	for tenantNum, tenant := range tenants {
		if tenant.Name == pod.Namespace {
			currentTenant = tenants[tenantNum]
			break
		}
	}
	if currentTenant.Name == "" {
		return nil, fmt.Errorf("Failed to find romana tenant with name %s, err=(%s)", pod.Namespace, err)
	}
	var segments []common.Segment
	segmentsUrl := fmt.Sprintf("%s/%d/segments", tenantUrl, currentTenant.ID)
	err = client.Get(segmentsUrl, &segments)
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch segments from %s, err=(%s)", segmentsUrl, err)
	}
	var currentSegment common.Segment
	for segmentNum, segment := range segments {
		if segment.Name == segmentLabel {
			currentSegment = segments[segmentNum]
			break
		}
	}
	if currentSegment.Name == "" {
		return nil, fmt.Errorf("Failed to discover segment %s within tenant %s", pod.Namespace, segmentLabel)
	}
	log.Infof("Discovered tenant=%d, segment=%d, host=%d", currentTenant.ID, currentSegment.ID, currentHost.ID)

	// IPAM allocate
	ipamUrl, err := client.GetServiceUrl("ipam")
	if err != nil {
		return nil, fmt.Errorf("Failed to discover ipam url from romana root err=(%s)", err)
	}
	ipamUrl += "/endpoints"
	var ipamReq, ipamResp common.IPAMEndpoint
	ipamReq = common.IPAMEndpoint{
		TenantID:  fmt.Sprintf("%d", currentTenant.ID),
		SegmentID: fmt.Sprintf("%d", currentSegment.ID),
		HostId:    fmt.Sprintf("%d", currentHost.ID),
		Name:      pod.Name,
	}
	err = client.Post(ipamUrl, &ipamReq, &ipamResp)
	if err != nil {
		return nil, fmt.Errorf("Failed to allocate IP address for a pod %s.%s, err=(%s)", pod.Namespace, currentTenant.Name, err)
	}
	log.Infof("Allocated IP address %s", ipamResp.Ip)
	ipamIP, err := netlink.ParseIPNet(ipamResp.Ip + "/32")
	if err != nil {
		return nil, fmt.Errorf("Failed to parse IP address %s, err=(%s)", ipamResp.Ip, err)
	}

	return ipamIP, nil
}

func (DefaultAddressManager) Deallocate(config NetConf, targetName string) error {
	// Rest client config
	clientConfig := common.GetDefaultRestClientConfig(config.RomanaRoot, nil)
	client, err := common.NewRestClient(clientConfig)
	if err != nil {
		return fmt.Errorf("Failed to reach romana root at %s, err=(%s)", config.RomanaRoot, err)
	}
	log.Infof("Created romana client %v", client)

	ipamUrl, err := client.GetServiceUrl("ipam")
	if err != nil {
		return fmt.Errorf("Failed to discover ipam url from romana root err=(%s)", err)
	}
	ipamUrl += "/endpoints"

	var endpoints, podEndpoints []common.IPAMEndpoint
	err = client.Get(ipamUrl, &endpoints)
	if err != nil {
		return fmt.Errorf("Failed to fetch ipam endpoints, err=(%s)", err)
	}

	for eNum, endpoint := range endpoints {
		if endpoint.Name == targetName && endpoint.InUse {
			podEndpoints = append(podEndpoints, endpoints[eNum])
		}
	}

	if len(podEndpoints) == 0 {
		return fmt.Errorf("No IPAM endpoints found for pod %s", targetName)
	}

	if len(podEndpoints) > 1 {
		return fmt.Errorf("Multiple IPAM endpoints found for pod %s, not supported", targetName)
	}

	endpointDeleteUrl := fmt.Sprintf("%s/%s", ipamUrl, podEndpoints[0].Ip)
	err = client.Delete(endpointDeleteUrl, nil, nil)
	if err != nil {
		return fmt.Errorf("Failed to delete IPAM endpoint %v", podEndpoints[0])
	}

	return nil
}
<commit_msg>Remove template for unimplemented feature<commit_after>// Copyright (c) 2017 Pani Networks
// All Rights Reserved.
//
// 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 provides tools for romana CNI plugin to interact with other
// Romana services.
package cni

import (
	"fmt"
	"net"

	"github.com/containernetworking/cni/pkg/types"
	"github.com/romana/core/common"
	log "github.com/romana/rlog"
	"github.com/vishvananda/netlink"
)

// RomanaAddresManager describes functions that allow allocating and deallocating
// IP addresses from Romana.
type RomanaAddresManager interface {
	Allocate(NetConf, RomanaAllocatorPodDescription) (*net.IPNet, error)
	Deallocate(NetConf, string) error
}

// NewRomanaAddressManager returns structure that satisfies RomanaAddresManager,
// it allows multiple implementations.
func NewRomanaAddressManager(provider RomanaAddressManagerProvider) (RomanaAddresManager, error) {
	if provider == DefaultProvider {
		return DefaultAddressManager{}, nil
	}

	return nil, fmt.Errorf("Unknown provider type %s", provider)
}

type RomanaAddressManagerProvider string

// DefaultProvider allocates and deallocates IP addresses using rest requests
// to Romana IPAM.
const DefaultProvider RomanaAddressManagerProvider = "default"

// RomanaAllocatorPodDescription represents collection of parameters used to allocate IP address.
type RomanaAllocatorPodDescription struct {
	Name        string
	Hostname    string
	Namespace   string
	Labels      map[string]string
	Annotations map[string]string
}

// NetConf represents parameters CNI plugin receives via stdin.
type NetConf struct {
	types.NetConf
	KubernetesConfig string `json:"kubernetes_config"`
	RomanaRoot       string `json:"romana_root"`

	// Name of a current host in romana.
	// If omitted, current hostname will be used.
	RomanaHostName   string `json:"romana_host_name"`
	SegmentLabelName string `json:"segment_label_name"`
	TenantLabelName  string `json:"tenant_label_name"` // TODO for stas, we don't use it. May be it should go away.
	UseAnnotations   bool   `json:"use_annotattions"`
}

type DefaultAddressManager struct{}

func (DefaultAddressManager) Allocate(config NetConf, pod RomanaAllocatorPodDescription) (*net.IPNet, error) {
	// Discover pod segment.
	var segmentLabel string
	var ok bool
	if config.UseAnnotations {
		segmentLabel, ok = pod.Annotations[config.SegmentLabelName]
	} else {
		segmentLabel, ok = pod.Labels[config.SegmentLabelName]
	}
	if !ok {
		return nil, fmt.Errorf("Failed to discover segment label for a pod")
	}
	log.Infof("Discovered segment %s for a pod", segmentLabel)

	// Rest client config
	clientConfig := common.GetDefaultRestClientConfig(config.RomanaRoot, nil)
	client, err := common.NewRestClient(clientConfig)
	if err != nil {
		return nil, fmt.Errorf("Failed to reach romana root at %s, err=(%s)", config.RomanaRoot, err)
	}
	log.Infof("Created romana client %v", client)

	// Topology, find host id.
	hosts, err := client.ListHosts()
	if err != nil {
		return nil, fmt.Errorf("Failed to list romana hosts err=(%s)", err)
	}
	var currentHost common.Host
	for hostNum, host := range hosts {
		if host.Name == config.RomanaHostName {
			currentHost = hosts[hostNum]
			break
		}
	}
	if currentHost.Name == "" {
		return nil, fmt.Errorf("Failed to find romana host with name %s in romana database", config.RomanaHostName)
	}

	// Tenant and segemnt
	tenantUrl, err := client.GetServiceUrl("tenant")
	if err != nil {
		return nil, fmt.Errorf("Failed to discover tenant url from romana root err=(%s)", err)
	}
	tenantUrl += "/tenants"
	var tenants []common.Tenant
	err = client.Get(tenantUrl, &tenants)
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch romana tenants from %s, err=(%s)", tenantUrl, err)
	}
	var currentTenant common.Tenant
	for tenantNum, tenant := range tenants {
		if tenant.Name == pod.Namespace {
			currentTenant = tenants[tenantNum]
			break
		}
	}
	if currentTenant.Name == "" {
		return nil, fmt.Errorf("Failed to find romana tenant with name %s, err=(%s)", pod.Namespace, err)
	}
	var segments []common.Segment
	segmentsUrl := fmt.Sprintf("%s/%d/segments", tenantUrl, currentTenant.ID)
	err = client.Get(segmentsUrl, &segments)
	if err != nil {
		return nil, fmt.Errorf("Failed to fetch segments from %s, err=(%s)", segmentsUrl, err)
	}
	var currentSegment common.Segment
	for segmentNum, segment := range segments {
		if segment.Name == segmentLabel {
			currentSegment = segments[segmentNum]
			break
		}
	}
	if currentSegment.Name == "" {
		return nil, fmt.Errorf("Failed to discover segment %s within tenant %s", pod.Namespace, segmentLabel)
	}
	log.Infof("Discovered tenant=%d, segment=%d, host=%d", currentTenant.ID, currentSegment.ID, currentHost.ID)

	// IPAM allocate
	ipamUrl, err := client.GetServiceUrl("ipam")
	if err != nil {
		return nil, fmt.Errorf("Failed to discover ipam url from romana root err=(%s)", err)
	}
	ipamUrl += "/endpoints"
	var ipamReq, ipamResp common.IPAMEndpoint
	ipamReq = common.IPAMEndpoint{
		TenantID:  fmt.Sprintf("%d", currentTenant.ID),
		SegmentID: fmt.Sprintf("%d", currentSegment.ID),
		HostId:    fmt.Sprintf("%d", currentHost.ID),
		Name:      pod.Name,
	}
	err = client.Post(ipamUrl, &ipamReq, &ipamResp)
	if err != nil {
		return nil, fmt.Errorf("Failed to allocate IP address for a pod %s.%s, err=(%s)", pod.Namespace, currentTenant.Name, err)
	}
	log.Infof("Allocated IP address %s", ipamResp.Ip)
	ipamIP, err := netlink.ParseIPNet(ipamResp.Ip + "/32")
	if err != nil {
		return nil, fmt.Errorf("Failed to parse IP address %s, err=(%s)", ipamResp.Ip, err)
	}

	return ipamIP, nil
}

func (DefaultAddressManager) Deallocate(config NetConf, targetName string) error {
	// Rest client config
	clientConfig := common.GetDefaultRestClientConfig(config.RomanaRoot, nil)
	client, err := common.NewRestClient(clientConfig)
	if err != nil {
		return fmt.Errorf("Failed to reach romana root at %s, err=(%s)", config.RomanaRoot, err)
	}
	log.Infof("Created romana client %v", client)

	ipamUrl, err := client.GetServiceUrl("ipam")
	if err != nil {
		return fmt.Errorf("Failed to discover ipam url from romana root err=(%s)", err)
	}
	ipamUrl += "/endpoints"

	var endpoints, podEndpoints []common.IPAMEndpoint
	err = client.Get(ipamUrl, &endpoints)
	if err != nil {
		return fmt.Errorf("Failed to fetch ipam endpoints, err=(%s)", err)
	}

	for eNum, endpoint := range endpoints {
		if endpoint.Name == targetName && endpoint.InUse {
			podEndpoints = append(podEndpoints, endpoints[eNum])
		}
	}

	if len(podEndpoints) == 0 {
		return fmt.Errorf("No IPAM endpoints found for pod %s", targetName)
	}

	if len(podEndpoints) > 1 {
		return fmt.Errorf("Multiple IPAM endpoints found for pod %s, not supported", targetName)
	}

	endpointDeleteUrl := fmt.Sprintf("%s/%s", ipamUrl, podEndpoints[0].Ip)
	err = client.Delete(endpointDeleteUrl, nil, nil)
	if err != nil {
		return fmt.Errorf("Failed to delete IPAM endpoint %v", podEndpoints[0])
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2021 The Kubernetes 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 cve

import (
	"errors"
	"fmt"
	"regexp"

	cvss "github.com/goark/go-cvss/v3/metric"
)

// CVE Information of a linked CVE vulnerability
type CVE struct {
	ID            string  `json:"id" yaml:"id"`                                 // CVE ID, eg CVE-2019-1010260
	Title         string  `json:"title" yaml:"title"`                           // Title of the vulnerability
	Description   string  `json:"description" yaml:"description"`               // Description text of the vulnerability
	TrackingIssue string  `json:"issue" yaml:"issue"`                           // Link to the vulnerability tracking issue (url, optional)
	CVSSVector    string  `json:"vector" yaml:"vector"`                         // Full CVSS vector string, CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H
	CVSSScore     float32 `json:"score" yaml:"score"`                           // Numeric CVSS score (eg 6.2)
	CVSSRating    string  `json:"rating" yaml:"rating"`                         // Severity bucket (eg Medium)
	CalcLink      string  `json:"calclink,omitempty" yaml:"calclink,omitempty"` // Link to the CVE calculator (automatic)
	LinkedPRs     []int   `json:"pullrequests"`                                 // List of linked PRs (to remove them from the release notes doc)
}

// ReadRawInterface populates the CVE data struct from the raw array
// as returned by the YAML parser
func (cve *CVE) ReadRawInterface(cvedata interface{}) error {
	if val, ok := cvedata.(map[interface{}]interface{})["id"].(string); ok {
		cve.ID = val
	}
	if val, ok := cvedata.(map[interface{}]interface{})["title"].(string); ok {
		cve.Title = val
	}
	if val, ok := cvedata.(map[interface{}]interface{})["issue"].(string); ok {
		cve.TrackingIssue = val
	}
	if val, ok := cvedata.(map[interface{}]interface{})["vector"].(string); ok {
		cve.CVSSVector = val
	}
	if val, ok := cvedata.(map[interface{}]interface{})["score"].(float64); ok {
		cve.CVSSScore = float32(val)
	}
	if val, ok := cvedata.(map[interface{}]interface{})["rating"].(string); ok {
		cve.CVSSRating = val
	}
	if val, ok := cvedata.(map[interface{}]interface{})["description"].(string); ok {
		cve.Description = val
	}
	// Linked PRs is a list of the PR IDs
	if val, ok := cvedata.(map[interface{}]interface{})["linkedPRs"].([]interface{}); ok {
		cve.LinkedPRs = []int{}
		for _, prid := range val {
			cve.LinkedPRs = append(cve.LinkedPRs, prid.(int))
		}
	}

	return nil
}

// Validate checks the data defined in a CVE map is complete and valid
func (cve *CVE) Validate() error {
	// Verify that rating is defined and a known string
	if cve.CVSSRating == "" {
		return errors.New("missing CVSS rating from CVE data")
	}

	// Check rating is a valid string
	if _, ok := map[string]bool{
		"None": true, "Low": true, "Medium": true, "High": true, "Critical": true,
	}[cve.CVSSRating]; !ok {
		return errors.New("invalid CVSS rating")
	}

	// Check vector string is not empty
	if cve.CVSSVector == "" {
		return errors.New("string CVSS vector missing from CVE data")
	}

	if len(cve.CVSSVector) == 44 {
		// Parse the vector string to make sure it is well formed
		bm, err := cvss.NewBase().Decode(cve.CVSSVector)
		if err != nil {
			return fmt.Errorf("parsing CVSS vector string: %w", err)
		}
		cve.CalcLink = fmt.Sprintf(
			"https://www.first.org/cvss/calculator/%s#%s", bm.Ver.String(), cve.CVSSVector,
		)
	} else {
		// Parse the vector string to make sure it is well formed
		bm, err := cvss.NewTemporal().Decode(cve.CVSSVector)
		if err != nil {
			return fmt.Errorf("parsing CVSS vector string: %w", err)
		}
		cve.CalcLink = fmt.Sprintf(
			"https://www.first.org/cvss/calculator/%s#%s", bm.Ver.String(), cve.CVSSVector,
		)
	}

	if cve.CVSSScore == 0 {
		return errors.New("missing CVSS score from CVE data")
	}
	if cve.CVSSScore < 0 || cve.CVSSScore > 10 {
		return errors.New("out of range CVSS score, should be 0.0 - 10.0")
	}

	if err := ValidateID(cve.ID); err != nil {
		return fmt.Errorf("checking CVE ID: %w", err)
	}

	// Title and description must not be empty
	if cve.Title == "" {
		return errors.New("title missing from CVE data")
	}

	if cve.Description == "" {
		return errors.New("missing CVE description from CVE data")
	}

	return nil
}

// ValidateID checks if a CVE IS string is valid
func ValidateID(cveID string) error {
	if cveID == "" {
		return errors.New("empty CVE ID string")
	}

	// Verify that the CVE ID is well formed
	if !regexp.MustCompile(CVEIDRegExp).MatchString(cveID) {
		return errors.New("not well formed CVS ID")
	}

	return nil
}
<commit_msg>Use CVE `Metrics` interface to simplify code<commit_after>/*
Copyright 2021 The Kubernetes 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 cve

import (
	"errors"
	"fmt"
	"regexp"

	cvss "github.com/goark/go-cvss/v3/metric"
)

// CVE Information of a linked CVE vulnerability
type CVE struct {
	ID            string  `json:"id" yaml:"id"`                                 // CVE ID, eg CVE-2019-1010260
	Title         string  `json:"title" yaml:"title"`                           // Title of the vulnerability
	Description   string  `json:"description" yaml:"description"`               // Description text of the vulnerability
	TrackingIssue string  `json:"issue" yaml:"issue"`                           // Link to the vulnerability tracking issue (url, optional)
	CVSSVector    string  `json:"vector" yaml:"vector"`                         // Full CVSS vector string, CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:H/I:H/A:H
	CVSSScore     float32 `json:"score" yaml:"score"`                           // Numeric CVSS score (eg 6.2)
	CVSSRating    string  `json:"rating" yaml:"rating"`                         // Severity bucket (eg Medium)
	CalcLink      string  `json:"calclink,omitempty" yaml:"calclink,omitempty"` // Link to the CVE calculator (automatic)
	LinkedPRs     []int   `json:"pullrequests"`                                 // List of linked PRs (to remove them from the release notes doc)
}

// ReadRawInterface populates the CVE data struct from the raw array
// as returned by the YAML parser
func (cve *CVE) ReadRawInterface(cvedata interface{}) error {
	if val, ok := cvedata.(map[interface{}]interface{})["id"].(string); ok {
		cve.ID = val
	}
	if val, ok := cvedata.(map[interface{}]interface{})["title"].(string); ok {
		cve.Title = val
	}
	if val, ok := cvedata.(map[interface{}]interface{})["issue"].(string); ok {
		cve.TrackingIssue = val
	}
	if val, ok := cvedata.(map[interface{}]interface{})["vector"].(string); ok {
		cve.CVSSVector = val
	}
	if val, ok := cvedata.(map[interface{}]interface{})["score"].(float64); ok {
		cve.CVSSScore = float32(val)
	}
	if val, ok := cvedata.(map[interface{}]interface{})["rating"].(string); ok {
		cve.CVSSRating = val
	}
	if val, ok := cvedata.(map[interface{}]interface{})["description"].(string); ok {
		cve.Description = val
	}
	// Linked PRs is a list of the PR IDs
	if val, ok := cvedata.(map[interface{}]interface{})["linkedPRs"].([]interface{}); ok {
		cve.LinkedPRs = []int{}
		for _, prid := range val {
			cve.LinkedPRs = append(cve.LinkedPRs, prid.(int))
		}
	}

	return nil
}

// Validate checks the data defined in a CVE map is complete and valid
func (cve *CVE) Validate() (err error) {
	// Verify that rating is defined and a known string
	if cve.CVSSRating == "" {
		return errors.New("missing CVSS rating from CVE data")
	}

	// Check rating is a valid string
	if _, ok := map[string]bool{
		"None": true, "Low": true, "Medium": true, "High": true, "Critical": true,
	}[cve.CVSSRating]; !ok {
		return errors.New("invalid CVSS rating")
	}

	// Check vector string is not empty
	if cve.CVSSVector == "" {
		return errors.New("string CVSS vector missing from CVE data")
	}

	var bm cvss.Metrics
	// Parse the vector string to make sure it is well formed
	if len(cve.CVSSVector) == 44 {
		bm, err = cvss.NewBase().Decode(cve.CVSSVector)
	} else {
		bm, err = cvss.NewTemporal().Decode(cve.CVSSVector)
	}
	if err != nil {
		return fmt.Errorf("parsing CVSS vector string: %w", err)
	}
	cve.CalcLink = fmt.Sprintf(
		"https://www.first.org/cvss/calculator/%s#%s", bm.BaseMetrics().Ver.String(), cve.CVSSVector,
	)

	if cve.CVSSScore == 0 {
		return errors.New("missing CVSS score from CVE data")
	}
	if cve.CVSSScore < 0 || cve.CVSSScore > 10 {
		return errors.New("out of range CVSS score, should be 0.0 - 10.0")
	}

	if err := ValidateID(cve.ID); err != nil {
		return fmt.Errorf("checking CVE ID: %w", err)
	}

	// Title and description must not be empty
	if cve.Title == "" {
		return errors.New("title missing from CVE data")
	}

	if cve.Description == "" {
		return errors.New("missing CVE description from CVE data")
	}

	return nil
}

// ValidateID checks if a CVE IS string is valid
func ValidateID(cveID string) error {
	if cveID == "" {
		return errors.New("empty CVE ID string")
	}

	// Verify that the CVE ID is well formed
	if !regexp.MustCompile(CVEIDRegExp).MatchString(cveID) {
		return errors.New("not well formed CVS ID")
	}

	return nil
}
<|endoftext|>


<commit_before>// Package log adds a thin wrapper around logrus to improve non-debug logging
// performance.
package log

import (
	"fmt"
	"io"

	"github.com/sirupsen/logrus"
)

var (
	l     = logrus.New()
	debug = false
)

// SetDebug controls debug logging.
func SetDebug(to bool) {
	debug = to
}

// SetFormatter sets the formatter.
func SetFormatter(to logrus.Formatter) {
	l.Formatter = to
}

// SetOutput sets the output.
func SetOutput(to io.Writer) {
	l.Out = to
}

// Fields is a map of logging fields.
type Fields map[string]interface{}

// LogFields implements Fielder for Fields.
func (f Fields) LogFields() Fields {
	return f
}

// A Fielder provides Fields via the LogFields method.
type Fielder interface {
	LogFields() Fields
}

// err is a wrapper around an error.
type err struct {
	e error
}

// LogFields provides Fields for logging.
func (e err) LogFields() Fields {
	return Fields{"error": e.e.Error()}
}

// Err is a wrapper around errors that implements Fielder.
func Err(e error) Fielder {
	return err{e}
}

// mergeFielders merges the Fields of multiple Fielders.
// Fields from the first Fielder will be used unchanged, Fields from subsequent
// Fielders will be prefixed with "%d.", starting from 1.
//
// must be called with len(fielders) > 0
func mergeFielders(fielders ...Fielder) logrus.Fields {
	if fielders[0] == nil {
		return nil
	}

	fields := fielders[0].LogFields()
	for i := 1; i < len(fielders); i++ {
		if fielders[i] == nil {
			continue
		}
		prefix := fmt.Sprint(i, ".")
		ff := fielders[i].LogFields()
		for k, v := range ff {
			fields[prefix+k] = v
		}
	}

	return logrus.Fields(fields)
}

// Debug logs at the debug level if debug logging is enabled.
func Debug(v interface{}, fielders ...Fielder) {
	if debug {
		if len(fielders) != 0 {
			l.WithFields(mergeFielders(fielders...)).Debug(v)
		} else {
			l.Debug(v)
		}
	}
}

// Info logs at the info level.
func Info(v interface{}, fielders ...Fielder) {
	if len(fielders) != 0 {
		l.WithFields(mergeFielders(fielders...)).Info(v)
	} else {
		l.Info(v)
	}
}

// Warn logs at the warning level.
func Warn(v interface{}, fielders ...Fielder) {
	if len(fielders) != 0 {
		l.WithFields(mergeFielders(fielders...)).Warn(v)
	} else {
		l.Warn(v)
	}
}

// Error logs at the error level.
func Error(v interface{}, fielders ...Fielder) {
	if len(fielders) != 0 {
		l.WithFields(mergeFielders(fielders...)).Error(v)
	} else {
		l.Error(v)
	}
}

// Fatal logs at the fatal level and exits with a status code != 0.
func Fatal(v interface{}, fielders ...Fielder) {
	if len(fielders) != 0 {
		l.WithFields(mergeFielders(fielders...)).Fatal(v)
	} else {
		l.Fatal(v)
	}
}
<commit_msg>pkg/log: fix debug logging<commit_after>// Package log adds a thin wrapper around logrus to improve non-debug logging
// performance.
package log

import (
	"fmt"
	"io"

	"github.com/sirupsen/logrus"
)

var (
	l     = logrus.New()
	debug = false
)

// SetDebug controls debug logging.
func SetDebug(to bool) {
	debug = to
	l.Level = logrus.DebugLevel
}

// SetFormatter sets the formatter.
func SetFormatter(to logrus.Formatter) {
	l.Formatter = to
}

// SetOutput sets the output.
func SetOutput(to io.Writer) {
	l.Out = to
}

// Fields is a map of logging fields.
type Fields map[string]interface{}

// LogFields implements Fielder for Fields.
func (f Fields) LogFields() Fields {
	return f
}

// A Fielder provides Fields via the LogFields method.
type Fielder interface {
	LogFields() Fields
}

// err is a wrapper around an error.
type err struct {
	e error
}

// LogFields provides Fields for logging.
func (e err) LogFields() Fields {
	return Fields{"error": e.e.Error()}
}

// Err is a wrapper around errors that implements Fielder.
func Err(e error) Fielder {
	return err{e}
}

// mergeFielders merges the Fields of multiple Fielders.
// Fields from the first Fielder will be used unchanged, Fields from subsequent
// Fielders will be prefixed with "%d.", starting from 1.
//
// must be called with len(fielders) > 0
func mergeFielders(fielders ...Fielder) logrus.Fields {
	if fielders[0] == nil {
		return nil
	}

	fields := fielders[0].LogFields()
	for i := 1; i < len(fielders); i++ {
		if fielders[i] == nil {
			continue
		}
		prefix := fmt.Sprint(i, ".")
		ff := fielders[i].LogFields()
		for k, v := range ff {
			fields[prefix+k] = v
		}
	}

	return logrus.Fields(fields)
}

// Debug logs at the debug level if debug logging is enabled.
func Debug(v interface{}, fielders ...Fielder) {
	if debug {
		if len(fielders) != 0 {
			l.WithFields(mergeFielders(fielders...)).Debug(v)
		} else {
			l.Debug(v)
		}
	}
}

// Info logs at the info level.
func Info(v interface{}, fielders ...Fielder) {
	if len(fielders) != 0 {
		l.WithFields(mergeFielders(fielders...)).Info(v)
	} else {
		l.Info(v)
	}
}

// Warn logs at the warning level.
func Warn(v interface{}, fielders ...Fielder) {
	if len(fielders) != 0 {
		l.WithFields(mergeFielders(fielders...)).Warn(v)
	} else {
		l.Warn(v)
	}
}

// Error logs at the error level.
func Error(v interface{}, fielders ...Fielder) {
	if len(fielders) != 0 {
		l.WithFields(mergeFielders(fielders...)).Error(v)
	} else {
		l.Error(v)
	}
}

// Fatal logs at the fatal level and exits with a status code != 0.
func Fatal(v interface{}, fielders ...Fielder) {
	if len(fielders) != 0 {
		l.WithFields(mergeFielders(fielders...)).Fatal(v)
	} else {
		l.Fatal(v)
	}
}
<|endoftext|>


<commit_before>package tar

import (
	"archive/tar"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"time"

	"github.com/golang/glog"

	"github.com/openshift/source-to-image/pkg/errors"
)

// defaultTimeout is the amount of time that the untar will wait for a tar
// stream to extract a single file. A timeout is needed to guard against broken
// connections in which it would wait for a long time to untar and nothing would happen
const defaultTimeout = 5 * time.Second

// defaultExclusionPattern is the pattern of files that will not be included in a tar
// file when creating one. By default it is any file inside a .git metadata directory
var defaultExclusionPattern = regexp.MustCompile("((^\\.git\\/)|(\\/.git\\/)|(\\/.git$))")

// Tar can create and extract tar files used in an STI build
type Tar interface {
	// CreateTarFile creates a tar file in the base directory
	// using the contents of dir directory
	// The name of the new tar file is returned if successful
	CreateTarFile(base, dir string) (string, error)

	// ExtractTarStream extracts files from a given tar stream.
	// Times out if reading from the stream for any given file
	// exceeds the value of timeout
	ExtractTarStream(dir string, reader io.Reader) error
}

// New creates a new Tar
func New() Tar {
	return &stiTar{
		exclude: defaultExclusionPattern,
		timeout: defaultTimeout,
	}
}

// stiTar is an implementation of the Tar interface
type stiTar struct {
	timeout time.Duration
	exclude *regexp.Regexp
}

// CreateTarFile creates a tar file from the given directory
// while excluding files that match the given exclusion pattern
// It returns the name of the created file
func (t *stiTar) CreateTarFile(base, dir string) (string, error) {
	tarFile, err := ioutil.TempFile(base, "tar")
	defer tarFile.Close()
	if err != nil {
		return "", err
	}
	if err = t.CreateTarStream(dir, tarFile); err != nil {
		return "", err
	}
	return tarFile.Name(), nil
}

func (t *stiTar) shouldExclude(path string) bool {
	return t.exclude != nil && t.exclude.MatchString(path)
}

// CreateTarStream creates a tar stream on the given writer from
// the given directory while excluding files that match the given
// exclusion pattern.
func (t *stiTar) CreateTarStream(dir string, writer io.Writer) error {
	tarWriter := tar.NewWriter(writer)
	defer tarWriter.Close()
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() && !t.shouldExclude(path) {
			// if file is a link just writing header info is enough
			if info.Mode()&os.ModeSymlink != 0 {
				if err := t.writeTarHeader(tarWriter, dir, path, info); err != nil {
					glog.Errorf("	Error writing header for %s: %v", info.Name(), err)
				}
				return nil
			}

			// regular files are copied into tar, if accessible
			file, err := os.Open(path)
			if err != nil {
				glog.Errorf("Ignoring file %s: %v", path, err)
				return nil
			}
			defer file.Close()
			if err := t.writeTarHeader(tarWriter, dir, path, info); err != nil {
				glog.Errorf("Error writing header for %s: %v", info.Name(), err)
				return nil
			}
			if _, err = io.Copy(tarWriter, file); err != nil {
				glog.Errorf("Error copying file %s to tar: %v", path, err)
				return err
			}
		}
		return nil
	})

	if err != nil {
		glog.Errorf("Error writing tar: %v", err)
		return err
	}

	return nil
}

// writeTarHeader writes tar header for given file, returns error if operation fails
func (t *stiTar) writeTarHeader(tarWriter *tar.Writer, dir string, path string, info os.FileInfo) error {
	var (
		link string
		err  error
	)
	if info.Mode()&os.ModeSymlink != 0 {
		link, err = os.Readlink(path)
		if err != nil {
			return err
		}
	}
	header, err := tar.FileInfoHeader(info, link)
	if err != nil {
		return err
	}
	header.Name = path[1+len(dir):]
	glog.V(3).Infof("Adding to tar: %s as %s", path, header.Name)
	if err = tarWriter.WriteHeader(header); err != nil {
		return err
	}

	return nil
}

// ExtractTarStream extracts files from a given tar stream.
// Times out if reading from the stream for any given file
// exceeds the value of timeout
func (t *stiTar) ExtractTarStream(dir string, reader io.Reader) error {
	tarReader := tar.NewReader(reader)
	errorChannel := make(chan error)
	timeout := t.timeout
	timeoutTimer := time.NewTimer(timeout)
	go func() {
		for {
			header, err := tarReader.Next()
			timeoutTimer.Reset(timeout)
			if err == io.EOF {
				errorChannel <- nil
				break
			}
			if err != nil {
				glog.Errorf("Error reading next tar header: %v", err)
				errorChannel <- err
				break
			}
			if header.FileInfo().IsDir() {
				dirPath := filepath.Join(dir, header.Name)
				if err = os.MkdirAll(dirPath, 0700); err != nil {
					glog.Errorf("Error creating dir %s: %v", dirPath, err)
					errorChannel <- err
					break
				}
			} else {
				fileDir := filepath.Dir(header.Name)
				dirPath := filepath.Join(dir, fileDir)
				if err = os.MkdirAll(dirPath, 0700); err != nil {
					glog.Errorf("Error creating dir %s: %v", dirPath, err)
					errorChannel <- err
					break
				}
				path := filepath.Join(dir, header.Name)
				glog.V(3).Infof("Creating %s", path)
				success := false
				// The file times need to be modified after it's been closed
				// thus this function is deferred before the file close
				defer func() {
					if success && os.Chtimes(path, time.Now(),
						header.FileInfo().ModTime()) != nil {
						glog.Errorf("Error setting file dates: %v", err)
						errorChannel <- err
					}
				}()
				file, err := os.Create(path)
				defer file.Close()
				if err != nil {
					glog.Errorf("Error creating file %s: %v", path, err)
					errorChannel <- err
					break
				}
				glog.V(3).Infof("Extracting/writing %s", path)
				written, err := io.Copy(file, tarReader)
				if err != nil {
					glog.Errorf("Error writing file: %v", err)
					errorChannel <- err
					break
				}
				if written != header.Size {
					message := fmt.Sprintf("Wrote %d bytes, expected to write %d",
						written, header.Size)
					glog.Errorf(message)
					errorChannel <- fmt.Errorf(message)
					break
				}
				if err = file.Chmod(header.FileInfo().Mode()); err != nil {
					glog.Errorf("Error setting file mode: %v", err)
					errorChannel <- err
					break
				}
				glog.V(3).Infof("Done with %s", path)
				success = true
			}
		}
	}()

	for {
		select {
		case err := <-errorChannel:
			if err != nil {
				glog.Errorf("Error extracting tar stream")
			} else {
				glog.V(2).Infof("Done extracting tar stream")
			}
			return err
		case <-timeoutTimer.C:
			return errors.NewTarTimeoutError()
		}
	}
}
<commit_msg>Issue 171 - fixed properly closing files upon untarring.<commit_after>package tar

import (
	"archive/tar"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"time"

	"github.com/golang/glog"

	"github.com/openshift/source-to-image/pkg/errors"
)

// defaultTimeout is the amount of time that the untar will wait for a tar
// stream to extract a single file. A timeout is needed to guard against broken
// connections in which it would wait for a long time to untar and nothing would happen
const defaultTimeout = 5 * time.Second

// defaultExclusionPattern is the pattern of files that will not be included in a tar
// file when creating one. By default it is any file inside a .git metadata directory
var defaultExclusionPattern = regexp.MustCompile("((^\\.git\\/)|(\\/.git\\/)|(\\/.git$))")

// Tar can create and extract tar files used in an STI build
type Tar interface {
	// CreateTarFile creates a tar file in the base directory
	// using the contents of dir directory
	// The name of the new tar file is returned if successful
	CreateTarFile(base, dir string) (string, error)

	// ExtractTarStream extracts files from a given tar stream.
	// Times out if reading from the stream for any given file
	// exceeds the value of timeout
	ExtractTarStream(dir string, reader io.Reader) error
}

// New creates a new Tar
func New() Tar {
	return &stiTar{
		exclude: defaultExclusionPattern,
		timeout: defaultTimeout,
	}
}

// stiTar is an implementation of the Tar interface
type stiTar struct {
	timeout time.Duration
	exclude *regexp.Regexp
}

// CreateTarFile creates a tar file from the given directory
// while excluding files that match the given exclusion pattern
// It returns the name of the created file
func (t *stiTar) CreateTarFile(base, dir string) (string, error) {
	tarFile, err := ioutil.TempFile(base, "tar")
	defer tarFile.Close()
	if err != nil {
		return "", err
	}
	if err = t.CreateTarStream(dir, tarFile); err != nil {
		return "", err
	}
	return tarFile.Name(), nil
}

func (t *stiTar) shouldExclude(path string) bool {
	return t.exclude != nil && t.exclude.MatchString(path)
}

// CreateTarStream creates a tar stream on the given writer from
// the given directory while excluding files that match the given
// exclusion pattern.
func (t *stiTar) CreateTarStream(dir string, writer io.Writer) error {
	tarWriter := tar.NewWriter(writer)
	defer tarWriter.Close()
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() && !t.shouldExclude(path) {
			// if file is a link just writing header info is enough
			if info.Mode()&os.ModeSymlink != 0 {
				if err := t.writeTarHeader(tarWriter, dir, path, info); err != nil {
					glog.Errorf("	Error writing header for %s: %v", info.Name(), err)
				}
				return nil
			}

			// regular files are copied into tar, if accessible
			file, err := os.Open(path)
			if err != nil {
				glog.Errorf("Ignoring file %s: %v", path, err)
				return nil
			}
			defer file.Close()
			if err := t.writeTarHeader(tarWriter, dir, path, info); err != nil {
				glog.Errorf("Error writing header for %s: %v", info.Name(), err)
				return nil
			}
			if _, err = io.Copy(tarWriter, file); err != nil {
				glog.Errorf("Error copying file %s to tar: %v", path, err)
				return err
			}
		}
		return nil
	})

	if err != nil {
		glog.Errorf("Error writing tar: %v", err)
		return err
	}

	return nil
}

// writeTarHeader writes tar header for given file, returns error if operation fails
func (t *stiTar) writeTarHeader(tarWriter *tar.Writer, dir string, path string, info os.FileInfo) error {
	var (
		link string
		err  error
	)
	if info.Mode()&os.ModeSymlink != 0 {
		link, err = os.Readlink(path)
		if err != nil {
			return err
		}
	}
	header, err := tar.FileInfoHeader(info, link)
	if err != nil {
		return err
	}
	header.Name = path[1+len(dir):]
	glog.V(3).Infof("Adding to tar: %s as %s", path, header.Name)
	if err = tarWriter.WriteHeader(header); err != nil {
		return err
	}

	return nil
}

// ExtractTarStream extracts files from a given tar stream.
// Times out if reading from the stream for any given file
// exceeds the value of timeout
func (t *stiTar) ExtractTarStream(dir string, reader io.Reader) error {
	tarReader := tar.NewReader(reader)
	errorChannel := make(chan error)
	timeout := t.timeout
	timeoutTimer := time.NewTimer(timeout)
	go func() {
		for {
			header, err := tarReader.Next()
			timeoutTimer.Reset(timeout)
			if err == io.EOF {
				errorChannel <- nil
				break
			}
			if err != nil {
				glog.Errorf("Error reading next tar header: %v", err)
				errorChannel <- err
				break
			}
			if header.FileInfo().IsDir() {
				dirPath := filepath.Join(dir, header.Name)
				if err = os.MkdirAll(dirPath, 0700); err != nil {
					glog.Errorf("Error creating dir %s: %v", dirPath, err)
					errorChannel <- err
					break
				}
			} else {
				fileDir := filepath.Dir(header.Name)
				dirPath := filepath.Join(dir, fileDir)
				if err = os.MkdirAll(dirPath, 0700); err != nil {
					glog.Errorf("Error creating dir %s: %v", dirPath, err)
					errorChannel <- err
					break
				}
				if err := extractFile(dir, header, tarReader); err != nil {
					glog.Errorf("Error extracting file %s: %v", header.Name, err)
					errorChannel <- err
				}
			}
		}
	}()

	for {
		select {
		case err := <-errorChannel:
			if err != nil {
				glog.Errorf("Error extracting tar stream")
			} else {
				glog.V(2).Infof("Done extracting tar stream")
			}
			return err
		case <-timeoutTimer.C:
			return errors.NewTarTimeoutError()
		}
	}
}

func extractFile(dir string, header *tar.Header, tarReader io.Reader) error {
	path := filepath.Join(dir, header.Name)
	glog.V(3).Infof("Creating %s", path)

	file, err := os.Create(path)
	// The file times need to be modified after it's been closed thus this function
	// is deferred after the file close (LIFO order for defer)
	defer os.Chtimes(path, time.Now(), header.FileInfo().ModTime())
	defer file.Close()
	if err != nil {
		return err
	}
	glog.V(3).Infof("Extracting/writing %s", path)
	written, err := io.Copy(file, tarReader)
	if err != nil {
		return err
	}
	if written != header.Size {
		return fmt.Errorf("Wrote %d bytes, expected to write %d", written, header.Size)
	}
	if err = file.Chmod(header.FileInfo().Mode()); err != nil {
		return err
	}

	glog.V(3).Infof("Done with %s", path)
	return nil
}
<|endoftext|>


<commit_before>package memdb

import (
	"context"
	"time"
)

// WatchSet is a collection of watch channels. The zero value is not usable.
// Use NewWatchSet to create a WatchSet.
type WatchSet map[<-chan struct{}]struct{}

// NewWatchSet constructs a new watch set.
func NewWatchSet() WatchSet {
	return make(map[<-chan struct{}]struct{})
}

// Add appends a watchCh to the WatchSet if non-nil.
func (w WatchSet) Add(watchCh <-chan struct{}) {
	if w == nil {
		return
	}

	if _, ok := w[watchCh]; !ok {
		w[watchCh] = struct{}{}
	}
}

// AddWithLimit appends a watchCh to the WatchSet if non-nil, and if the given
// softLimit hasn't been exceeded. Otherwise, it will watch the given alternate
// channel. It's expected that the altCh will be the same on many calls to this
// function, so you will exceed the soft limit a little bit if you hit this, but
// not by much.
//
// This is useful if you want to track individual items up to some limit, after
// which you watch a higher-level channel (usually a channel from start of
// an iterator higher up in the radix tree) that will watch a superset of items.
func (w WatchSet) AddWithLimit(softLimit int, watchCh <-chan struct{}, altCh <-chan struct{}) {
	// This is safe for a nil WatchSet so we don't need to check that here.
	if len(w) < softLimit {
		w.Add(watchCh)
	} else {
		w.Add(altCh)
	}
}

// Watch blocks until one of the channels in the watch set is closed, or
// timeoutCh sends a value.
// Returns true if timeoutCh is what caused Watch to unblock.
func (w WatchSet) Watch(timeoutCh <-chan time.Time) bool {
	if w == nil {
		return false
	}

	// Create a context that gets cancelled when the timeout is triggered
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go func() {
		select {
		case <-timeoutCh:
			cancel()
		case <-ctx.Done():
		}
	}()

	return w.WatchCtx(ctx) == context.Canceled
}

// WatchCtx blocks until one of the channels in the watch set is closed, or
// ctx is done (cancelled or exceeds the deadline). WatchCtx returns an error
// if the ctx causes it to unblock, otherwise returns nil.
//
// WatchCtx should be preferred over Watch.
func (w WatchSet) WatchCtx(ctx context.Context) error {
	if w == nil {
		return nil
	}

	if n := len(w); n <= aFew {
		idx := 0
		chunk := make([]<-chan struct{}, aFew)
		for watchCh := range w {
			chunk[idx] = watchCh
			idx++
		}
		return watchFew(ctx, chunk)
	}

	return w.watchMany(ctx)
}

// watchMany is used if there are many watchers.
func (w WatchSet) watchMany(ctx context.Context) error {
	// Set up a goroutine for each watcher.
	triggerCh := make(chan struct{}, 1)
	watcher := func(chunk []<-chan struct{}) {
		if err := watchFew(ctx, chunk); err == nil {
			select {
			case triggerCh <- struct{}{}:
			default:
			}
		}
	}

	// Apportion the watch channels into chunks we can feed into the
	// watchFew helper.
	idx := 0
	chunk := make([]<-chan struct{}, aFew)
	for watchCh := range w {
		subIdx := idx % aFew
		chunk[subIdx] = watchCh
		idx++

		// Fire off this chunk and start a fresh one.
		if idx%aFew == 0 {
			go watcher(chunk)
			chunk = make([]<-chan struct{}, aFew)
		}
	}

	// Make sure to watch any residual channels in the last chunk.
	if idx%aFew != 0 {
		go watcher(chunk)
	}

	// Wait for a channel to trigger or timeout.
	select {
	case <-triggerCh:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

// WatchCh returns a channel that is used to wait for any channel of the watch set to trigger
// or for the context to be cancelled. WatchCh creates a new goroutine each call, so
// callers may need to cache the returned channel to avoid creating extra goroutines.
func (w WatchSet) WatchCh(ctx context.Context) <-chan error {
	// Create the outgoing channel
	triggerCh := make(chan error, 1)

	// Create a goroutine to collect the error from WatchCtx
	go func() {
		triggerCh <- w.WatchCtx(ctx)
	}()

	return triggerCh
}
<commit_msg>Fix goroutine leak in WatchSet.watchMany<commit_after>package memdb

import (
	"context"
	"time"
)

// WatchSet is a collection of watch channels. The zero value is not usable.
// Use NewWatchSet to create a WatchSet.
type WatchSet map[<-chan struct{}]struct{}

// NewWatchSet constructs a new watch set.
func NewWatchSet() WatchSet {
	return make(map[<-chan struct{}]struct{})
}

// Add appends a watchCh to the WatchSet if non-nil.
func (w WatchSet) Add(watchCh <-chan struct{}) {
	if w == nil {
		return
	}

	if _, ok := w[watchCh]; !ok {
		w[watchCh] = struct{}{}
	}
}

// AddWithLimit appends a watchCh to the WatchSet if non-nil, and if the given
// softLimit hasn't been exceeded. Otherwise, it will watch the given alternate
// channel. It's expected that the altCh will be the same on many calls to this
// function, so you will exceed the soft limit a little bit if you hit this, but
// not by much.
//
// This is useful if you want to track individual items up to some limit, after
// which you watch a higher-level channel (usually a channel from start of
// an iterator higher up in the radix tree) that will watch a superset of items.
func (w WatchSet) AddWithLimit(softLimit int, watchCh <-chan struct{}, altCh <-chan struct{}) {
	// This is safe for a nil WatchSet so we don't need to check that here.
	if len(w) < softLimit {
		w.Add(watchCh)
	} else {
		w.Add(altCh)
	}
}

// Watch blocks until one of the channels in the watch set is closed, or
// timeoutCh sends a value.
// Returns true if timeoutCh is what caused Watch to unblock.
func (w WatchSet) Watch(timeoutCh <-chan time.Time) bool {
	if w == nil {
		return false
	}

	// Create a context that gets cancelled when the timeout is triggered
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	go func() {
		select {
		case <-timeoutCh:
			cancel()
		case <-ctx.Done():
		}
	}()

	return w.WatchCtx(ctx) == context.Canceled
}

// WatchCtx blocks until one of the channels in the watch set is closed, or
// ctx is done (cancelled or exceeds the deadline). WatchCtx returns an error
// if the ctx causes it to unblock, otherwise returns nil.
//
// WatchCtx should be preferred over Watch.
func (w WatchSet) WatchCtx(ctx context.Context) error {
	if w == nil {
		return nil
	}

	if n := len(w); n <= aFew {
		idx := 0
		chunk := make([]<-chan struct{}, aFew)
		for watchCh := range w {
			chunk[idx] = watchCh
			idx++
		}
		return watchFew(ctx, chunk)
	}

	return w.watchMany(ctx)
}

// watchMany is used if there are many watchers.
func (w WatchSet) watchMany(ctx context.Context) error {
	// Cancel all watcher goroutines when return.
	watcherCtx, cancel := context.WithCancel(ctx)
	defer cancel()

	// Set up a goroutine for each watcher.
	triggerCh := make(chan struct{}, 1)
	watcher := func(chunk []<-chan struct{}) {
		if err := watchFew(watcherCtx, chunk); err == nil {
			select {
			case triggerCh <- struct{}{}:
			default:
			}
		}
	}

	// Apportion the watch channels into chunks we can feed into the
	// watchFew helper.
	idx := 0
	chunk := make([]<-chan struct{}, aFew)
	for watchCh := range w {
		subIdx := idx % aFew
		chunk[subIdx] = watchCh
		idx++

		// Fire off this chunk and start a fresh one.
		if idx%aFew == 0 {
			go watcher(chunk)
			chunk = make([]<-chan struct{}, aFew)
		}
	}

	// Make sure to watch any residual channels in the last chunk.
	if idx%aFew != 0 {
		go watcher(chunk)
	}

	// Wait for a channel to trigger or timeout.
	select {
	case <-triggerCh:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

// WatchCh returns a channel that is used to wait for any channel of the watch set to trigger
// or for the context to be cancelled. WatchCh creates a new goroutine each call, so
// callers may need to cache the returned channel to avoid creating extra goroutines.
func (w WatchSet) WatchCh(ctx context.Context) <-chan error {
	// Create the outgoing channel
	triggerCh := make(chan error, 1)

	// Create a goroutine to collect the error from WatchCtx
	go func() {
		triggerCh <- w.WatchCtx(ctx)
	}()

	return triggerCh
}
<|endoftext|>