-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbind-service.html.md.erb
218 lines (165 loc) · 7.36 KB
/
bind-service.html.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
---
title: Bind a Service to Your App
owner: Tobias Fuhrimann
---
<strong><%= modified_date %></strong>
The <a href="../service-offerings/index.html" target="_blank">service marketplace</a> has a large number of data stores, from Redis and MongoDB, to MariaDB (fork of MySQL) and RabbitMQ. You can run `cf marketplace` to get an overview. In this step you will add a small MongoDB database to your app. When finished, we'll have an app to create and list cute kittens.
Create the database:
<pre class="terminal">
$ cf create-service mongodb-2 small my-mongodb
Creating service instance my-mongodb in org MyOrg / space MySpace as user@mydomain.com...
OK
Create in progress. Use 'cf services' or 'cf service my-mongodb' to check operation status.
Attention: The plan `small` of service `mongodb-2` is not free. The instance `my-mongodb` will incur a cost. Contact your administrator if you think this is in error.
</pre>
This creates a small MongoDB database for you which we now have to bind to our application. Binding means that the credentials and URL of the service will be written dynamically into the environment variables of the app as `VCAP_SERVICES` and can hence be used directly from there.
Let's bind the new service to our existing application:
<pre class="terminal">
$ cf bind-service my-go-app my-mongodb
Binding service my-mongodb to app my-go-app in org MyOrg / space MySpace as user@mydomain.com...
OK
TIP: Use 'cf restage my-go-app' to ensure your env variable changes take effect
</pre>
<p class="note">
<strong>Note</strong>: If you are getting <code>Server error, status code: 409</code>, please try again after a couple of minutes. It takes a while to spin up that MongoDB for you.
</p>
After that, we restage the application as suggested so that it includes the new credentials in its environment variables:
<pre class="terminal">
$ cf restage my-go-app
Restaging app my-go-app in org MyOrg / space MySpace as user@mydomain.com...
-----> Downloaded app package (8.0K)
-------> Buildpack version 1.5.8
-----> Creating runtime environment
...
</pre>
Now we want to consume our new MongoDB from within our application. Use glide to add the `mgo` and the `go-cfenv` packages to your dependencies:
<pre class="terminal">
$ glide get gopkg.in/mgo.v2 github.com/cloudfoundry-community/go-cfenv
[INFO] Preparing to install 2 packages.
...
</pre>
Now edit your `main.go` file to use these packages to connect to the database specified in your `VCAP_SERVICES` environment variable. We do this by importing the packages and adding a route for the `/kittens` endpoint which returns the `kittens` collection of our MongoDB.
First, add the needed dependencies to your `import` statement:
```go
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/cloudfoundry-community/go-cfenv"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
```
Now we need to know what a kitten looks like. Create a struct for them:
```go
// Kitten is a cute kitty
type Kitten struct {
ID bson.ObjectId `bson:"_id,omitempty" json:"_id"`
Name string `json:"name"`
}
// Kittens is an array of kittens
type Kittens []Kitten
```
Then we create another struct to hold a server with an mgo session:
```go
// Server is a server with an mgo session
type Server struct {
db *mgo.Session
}
```
As a next step, we need to talk to our database that we created above. In the `main` function, Create a variable `dbString` and dynamically assign it to the value of our db's connection string. We then create a session from that and attach the session to our server `s`. This server allows us to inject the session into the kitten handler so that we don't have to call `Dial` on every request. At the bottom, we add the kitten handler to handle the `/kittens` endpoint. Your `main` function should now look something like this:
```go
func main() {
var dbString string
if vcapServices := os.Getenv("VCAP_SERVICES"); len(vcapServices) == 0 {
dbString = "localhost"
} else {
appEnv, err := cfenv.Current()
if err != nil {
log.Fatal(err)
}
dbService, err := appEnv.Services.WithName("my-mongodb")
if err != nil {
log.Fatal(err)
}
uri, ok := dbService.Credentials["uri"].(string)
if !ok {
log.Fatal("no valid databse URI found")
}
dbString = uri
}
session, err := mgo.Dial(dbString)
if err != nil {
log.Fatal(err)
}
defer session.Close()
s := Server{db: session}
http.HandleFunc("/", IndexHandler)
http.HandleFunc("/kittens", s.KittenHandler)
var port string
if port = os.Getenv("PORT"); len(port) == 0 {
port = "8080"
}
log.Fatal(http.ListenAndServe(":"+port, nil))
}
```
The line `if vcapServices := os.Getenv("VCAP_SERVICES"); len(vcapServices) == 0 {` allows you to check if the app is running in the cloud. If not, it's using a local MongoDB and if it is, it's using the one from `VCAP_SERVICES` through the `cfenv` package.
Then we create the handler for our new `/kittens` endpoint. It handles either `GET` or `POST` requests. Create the following function:
```go
// KittenHandler allows to manage kittens
func (s *Server) KittenHandler(w http.ResponseWriter, r *http.Request) {
sess := s.db.Clone()
defer sess.Close()
c := sess.DB("").C("kittens")
switch r.Method {
case "GET":
var kittens Kittens
err := c.Find(bson.M{}).All(&kittens)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
err = json.NewEncoder(w).Encode(kittens)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
case "POST":
var kitten Kitten
err := json.NewDecoder(r.Body).Decode(&kitten)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = c.Insert(kitten)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusCreated)
fmt.Fprintf(w, "Kitten '%s' created", kitten.Name)
default:
http.Error(w, "Not Found", http.StatusNotFound)
}
}
```
This ensures that when you access your app using the `/kittens` route, it will return all the kittens stored in your database. Currently there are no kittens so it will return an empty array. Sad...
But you can create new kittens by POST-ing to the `/kittens` endpoint with the kitten's name as a URL query parameter. You can do so using curl or any similar tool:
<pre class="terminal">
$ curl -X POST "http://localhost:8080/kittens" -d '{"name":"garfield"}' --header "Content-Type: application/json"
</pre>
and then retrieve your new kitten at the `GET /kittens` endpoint.
Now, all you need to do is push your app to the cloud:
<pre class="terminal">
$ cf push my-go-app
</pre>
You can access other services like Redis or MariaDB in a similar matter, simply by binding them to your app and accessing them through the environment variables.
<div style="text-align:center;padding:3em;">
<a href="./manifest.html" class="btn btn-primary">I've bound a service to my App</a>
</div>