Make subscriptions Kubernetes-ready.
Update our .hgignore file to include the docker-ready subscripionsd binary
(which differs from the local subscriptionsd binary only in that it's
statically-compiled for linux).
Add a replication controller for subscriptionsd that will spin up the
appropriate pods for us and make sure our Stripe and Subscriptionsd secret
volumes are attached, so the pods may configure themselves with that private
info.
Create a Stripe secret volume with a placeholder for the stripe secret key.
Create a Subscriptionsd secret volume with the DSN sent to the base64 encoding
of an empty string right now (which means subscriptionsd will store data in
memory, but whatever.)
Create a subscriptionsd service that will route traffic to the subscriptionsd
pods created by the replication controller.
Create a minimal Dockerfile to get the subscriptionsd binary running on
kubernetes.
Add a build-docker helper script that will compile a Docker-ready subscriptionsd
binary (by compiling it in a Docker container).
Copy a Ubuntu ca-certificates.crt file that we'll inject into our Dockerfile so
the Stripe SSL can be resolved. Busybox doesn't have any certificates, by
default.
Create a wrapper script that will be run in the Docker container to read the
secrets from our secret volumes and inject them as environment variables, which
is what subscriptionsd understands.
6 "code.secondbit.org/api.hg"
7 "code.secondbit.org/ducky/subscriptions.hg"
8 "code.secondbit.org/trout.hg"
9 "code.secondbit.org/uuid.hg"
11 "golang.org/x/net/context"
15 SubscriptionScope = "subscriptions"
16 SubscriptionAdminScope = "subscriptions_admin"
19 func HandleSubscriptions(router *trout.Router, c context.Context) {
20 router.Endpoint("/subscriptions").Methods("POST", "OPTIONS").Handler(
21 api.CORSMiddleware(api.NegotiateMiddleware(api.ContextWrapper(c, CreateSubscriptionHandler))))
22 router.Endpoint("/subscriptions/{id}").Methods("GET", "OPTIONS").Handler(
23 api.CORSMiddleware(api.NegotiateMiddleware(api.ContextWrapper(c, GetSubscriptionHandler))))
26 func CreateSubscriptionHandler(w http.ResponseWriter, r *http.Request, c context.Context) {
27 store, err := getSubscriptionStore(c)
29 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
32 stripe, err := getStripeClient(c)
34 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
37 if !api.CheckScopes(r, SubscriptionScope) {
38 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
41 userID, err := api.AuthUser(r)
43 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
46 var req subscriptions.SubscriptionRequest
47 err = api.Decode(r, &req)
49 api.Encode(w, r, http.StatusBadRequest, Response{Errors: api.InvalidFormatError})
52 errs := req.Validate(userID, api.CheckScopes(r, SubscriptionAdminScope))
54 api.Encode(w, r, http.StatusBadRequest, Response{Errors: errs})
57 // TODO: need some way of validating the email they sent actually belongs to them
58 sub, err := subscriptions.New(req, stripe, store)
60 var rErr api.RequestError
63 case subscriptions.ErrSubscriptionAlreadyExists:
64 rErr = api.RequestError{Slug: api.RequestErrConflict, Field: "/user_id"}
65 code = http.StatusBadRequest
66 case subscriptions.ErrStripeSubscriptionAlreadyExists:
67 rErr = api.RequestError{Slug: api.RequestErrConflict, Field: "/stripe_token"}
68 code = http.StatusBadRequest
70 rErr = api.RequestError{Slug: api.RequestErrActOfGod}
71 code = http.StatusInternalServerError
73 api.Encode(w, r, code, Response{Errors: []api.RequestError{rErr}})
76 resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
77 api.Encode(w, r, http.StatusCreated, resp)
80 func GetSubscriptionHandler(w http.ResponseWriter, r *http.Request, c context.Context) {
81 store, err := getSubscriptionStore(c)
83 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
86 vars := trout.RequestVars(r)
87 rawID := vars.Get("id")
89 api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrMissing, Param: "id"}}})
92 id, err := uuid.Parse(rawID)
94 api.Encode(w, r, http.StatusBadRequest, Response{Errors: []api.RequestError{{Slug: api.RequestErrInvalidFormat, Param: "id"}}})
97 if !api.CheckScopes(r, SubscriptionScope) {
98 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
101 userID, err := api.AuthUser(r)
103 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
106 if !id.Equal(userID) && !api.CheckScopes(r, SubscriptionAdminScope) {
107 api.Encode(w, r, http.StatusUnauthorized, Response{Errors: []api.RequestError{{Slug: api.RequestErrAccessDenied}}})
110 subs, err := store.GetSubscriptions([]uuid.ID{id})
112 api.Encode(w, r, http.StatusInternalServerError, Response{Errors: api.ActOfGodError})
115 sub, ok := subs[id.String()]
117 api.Encode(w, r, http.StatusNotFound, Response{Errors: []api.RequestError{{Slug: api.RequestErrNotFound, Param: "id"}}})
120 resp := Response{Subscriptions: []subscriptions.Subscription{sub}}
121 api.Encode(w, r, http.StatusOK, resp)