Skip to content

Commit

Permalink
Update makefile for build, add admin command
Browse files Browse the repository at this point in the history
Former-commit-id: 82b6c5d
  • Loading branch information
jcbwlkr committed Jan 16, 2019
1 parent 6835121 commit 2128d27
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 43 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
private.pem
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,36 @@ The service provides record keeping for someone running a multi-family garage sa

<!--(Diagram generated with draw.io using `models.xml` file)-->

### Making Requests

#### Initial User

To make a request to the service you must have an authenticated user. Users can be created with the API but an initial admin user must first be created. While the service is running you can create the initial user with the command `make admin`

```
$ make admin
```

This will create a user with email `[email protected]` and password `gophers`.

#### Authenticating

Before any authenticated requests can be sent you must acquire an auth token. Make a request using HTTP Basic auth with your email and password to get the token.

```
$ curl --user "[email protected]:gophers" http://localhost:3000/v1/users/token
```

I suggest putting the resulting token in an environment variable like `$TOKEN`.

#### Authenticated Requests

To make authenticated requests put the token in the `Authorization` header with the `Bearer ` prefix.

```
$ curl -H "Authorization: Bearer ${TOKEN}" http://localhost:3000/v1/users
```

## What's Next

We are in the process of writing more documentation about this code. Classes are being finalized as part of the Ultimate series.
137 changes: 137 additions & 0 deletions cmd/sales-admin/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// This program performs administrative tasks for the garage sale service.
//
// Run it with --cmd keygen or --cmd useradd

package main

import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"log"
"os"
"time"

"github.com/ardanlabs/service/internal/platform/auth"
"github.com/ardanlabs/service/internal/platform/db"
"github.com/ardanlabs/service/internal/platform/flag"
"github.com/ardanlabs/service/internal/user"
"github.com/kelseyhightower/envconfig"
"github.com/pkg/errors"
)

func main() {

// =========================================================================
// Logging

log := log.New(os.Stdout, "sales-admin : ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile)

// =========================================================================
// Configuration

var cfg struct {
CMD string `envconfig:"CMD"`
DB struct {
DialTimeout time.Duration `default:"5s" envconfig:"DIAL_TIMEOUT"`
Host string `default:"localhost:27017/gotraining" envconfig:"HOST"`
}
Auth struct {
PrivateKeyFile string `default:"private.pem" envconfig:"PRIVATE_KEY_FILE"`
}
User struct {
Email string
Password string
}
}

if err := envconfig.Process("SALES", &cfg); err != nil {
log.Fatalf("main : Parsing Config : %v", err)
}

if err := flag.Process(&cfg); err != nil {
if err != flag.ErrHelp {
log.Fatalf("main : Parsing Command Line : %v", err)
}
return // We displayed help.
}

var err error
switch cfg.CMD {
case "keygen":
err = keygen(cfg.Auth.PrivateKeyFile)
case "useradd":
err = useradd(cfg.DB.Host, cfg.DB.DialTimeout, cfg.User.Email, cfg.User.Password)
default:
err = errors.New("Must provide --cmd keygen or --cmd useradd")
}

if err != nil {
log.Fatal(err)
}
}

// keygen creates an x509 private key for signing auth tokens.
func keygen(path string) error {

key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return errors.Wrap(err, "generating keys")
}

file, err := os.Create(path)
if err != nil {
return errors.Wrap(err, "creating private file")
}

block := pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
}

if err := pem.Encode(file, &block); err != nil {
return errors.Wrap(err, "encoding to private file")
}

if err := file.Close(); err != nil {
return errors.Wrap(err, "closing private file")
}

return nil
}

func useradd(dbHost string, dbTimeout time.Duration, email, pass string) error {

dbConn, err := db.New(dbHost, dbTimeout)
if err != nil {
return err
}
defer dbConn.Close()

if email == "" {
return errors.New("Must provide --user_email")
}
if pass == "" {
return errors.New("Must provide --user_password or set the env var SALES_USER_PASSWORD")
}

ctx := context.Background()

newU := user.NewUser{
Email: email,
Password: pass,
PasswordConfirm: pass,
Roles: []string{auth.RoleAdmin, auth.RoleUser},
}

usr, err := user.Create(ctx, dbConn, &newU, time.Now())
if err != nil {
return err
}

fmt.Printf("User created with id: %v\n", usr.ID.Hex())
return nil
}
1 change: 0 additions & 1 deletion cmd/sales-api/handlers/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ func API(log *log.Logger, masterDB *db.DB, authenticator *auth.Authenticator) ht
Authenticator: authenticator,
}

// TODO(jlw) Figure out why the order of these was reversed and maybe reverse it back.
app := web.New(log, mid.RequestLogger, mid.Metrics, mid.ErrorHandler)

// Register health check endpoint. This route is not authenticated.
Expand Down
10 changes: 5 additions & 5 deletions cmd/sales-api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ expvarmon -ports=":3001" -endpoint="/metrics" -vars="requests,goroutines,errors,
/*
Need to figure out timeouts for http service.
You might want to reset your DB_HOST env var during test tear down.
Add pulling git version from build command line.
Service should start even without a DB running yet.
symbols in profiles: https://github.com/golang/go/issues/23376 / https://github.com/google/pprof/pull/366
*/

// build is the git version of this program. It is set using build flags in the makefile.
var build = "develop"

func main() {

// =========================================================================
Expand Down Expand Up @@ -68,7 +70,7 @@ func main() {
}
Auth struct {
KeyID string `envconfig:"KEY_ID"`
PrivateKeyFile string `envconfig:"PRIVATE_KEY_FILE"`
PrivateKeyFile string `default:"private.pem" envconfig:"PRIVATE_KEY_FILE"`
Algorithm string `default:"RS256" envconfig:"ALGORITHM"`
}
}
Expand All @@ -87,7 +89,7 @@ func main() {
// =========================================================================
// App Starting

log.Println("main : Started : Application Initializing")
log.Printf("main : Started : Application Initializing version %q", build)
defer log.Println("main : Completed")

cfgJSON, err := json.MarshalIndent(cfg, "", " ")
Expand All @@ -112,8 +114,6 @@ func main() {
log.Fatalf("main : Parsing auth private key : %v", err)
}

// TODO(jlw) Do we really want to support rolling keys etc? We can use the naive NewSingleKeyFunc to keep things easy but leave in a note that says this is where you would need to add rotating key support.
// TODO(jlw) Do we need to explicitly pass a public key in from the config? Since we already have the private key we can just compute the public key when needed.
publicKeyLookup := auth.NewSingleKeyFunc(cfg.Auth.KeyID, key.Public().(*rsa.PublicKey))

authenticator, err := auth.NewAuthenticator(key, cfg.Auth.KeyID, cfg.Auth.Algorithm, publicKeyLookup)
Expand Down
20 changes: 4 additions & 16 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,22 @@ services:

# This is the core CRUD based service.
sales-api:
build:
context: .
args:
packagename: sales-api
container_name: sales-api
networks:
- shared-network
image: sales-api-amd64:1.0
ports:
- 3000:3000 # CRUD API
- 4000:4000 # DEBUG API
# environment:
# - SALES_DB_HOST=docker.for.mac.localhost:27017
volumes:
- ${PWD}/private.pem:/app/private.pem
environment:
- SALES_AUTH_KEY_ID=1
# - SALES_DB_HOST=got:[email protected]:39441/gotraining
# - GODEBUG=gctrace=1

# This sidecar publishes metrics to the console by default.
metrics:
build:
context: .
args:
packagename: metrics
packageprefix: sidecar/
container_name: metrics
networks:
- shared-network
Expand All @@ -55,11 +48,6 @@ services:

# This sidecar publishes tracing to the console by default.
tracer:
build:
context: .
args:
packagename: tracer
packageprefix: sidecar/
container_name: tracer
networks:
- shared-network
Expand Down
30 changes: 15 additions & 15 deletions dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@

FROM golang:1.10.3 as build
ENV CGO_ENABLED 0
ARG packagename
ARG packageprefix
ARG VCS_REF
ARG PACKAGE_NAME
ARG PACKAGE_PREFIX
RUN mkdir -p /go/src/github.com/ardanlabs/service
COPY . /go/src/github.com/ardanlabs/service
WORKDIR /go/src/github.com/ardanlabs/service/cmd/${packageprefix}${packagename}
RUN go build -ldflags "-s -w -X main.build=$(git rev-parse HEAD)" -a -tags netgo
WORKDIR /go/src/github.com/ardanlabs/service/cmd/${PACKAGE_PREFIX}${PACKAGE_NAME}
RUN go build -ldflags "-s -w -X main.build=${VCS_REF}" -a -tags netgo


# Run the Go Binary in Alpine.

FROM alpine:3.7
ARG BUILD_DATE
ARG VCS_REF
ARG packagename
ARG packageprefix
EXPOSE 3001
EXPOSE 4001
COPY --from=build /go/src/github.com/ardanlabs/service/cmd/${packageprefix}${packagename}/${packagename} /bin/package
ENTRYPOINT /bin/package
ARG PACKAGE_NAME
ARG PACKAGE_PREFIX
COPY --from=build /go/src/github.com/ardanlabs/service/cmd/${PACKAGE_PREFIX}${PACKAGE_NAME}/${PACKAGE_NAME} /app/main
WORKDIR /app
CMD /app/main

LABEL org.opencontainers.image.created=$BUILD_DATE \
org.opencontainers.image.title=${packagename} \
LABEL org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.title="${PACKAGE_NAME}" \
org.opencontainers.image.authors="William Kennedy <[email protected]>" \
org.opencontainers.image.source="https://github.com/ardanlabs/service/cmd/${packageprefix}${packagename}" \
org.opencontainers.image.revision=$VCS_REF \
org.opencontainers.image.vendor="Ardan Labs"
org.opencontainers.image.source="https://github.com/ardanlabs/service/cmd/${PACKAGE_PREFIX}${PACKAGE_NAME}" \
org.opencontainers.image.revision="${VCS_REF}" \
org.opencontainers.image.vendor="Ardan Labs"
18 changes: 12 additions & 6 deletions makefile
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
SHELL := /bin/bash

all: sales-api metrics tracer
all: keys sales-api metrics tracer

keys:
go run ./cmd/sales-admin/main.go --cmd keygen

admin:
go run ./cmd/sales-admin/main.go --cmd useradd --user_email [email protected] --user_password gophers

sales-api:
cd "$$GOPATH/src/github.com/ardanlabs/service"
docker build \
-t sales-api-amd64:1.0 \
-f dockerfile.sales-api \
--build-arg PACKAGE_NAME=sales-api \
--build-arg VCS_REF=`git rev-parse HEAD` \
--build-arg BUILD_DATE=`date -u +”%Y-%m-%dT%H:%M:%SZ”` \
.
docker system prune -f

metrics:
cd "$$GOPATH/src/github.com/ardanlabs/service"
docker build \
-t metrics-amd64:1.0 \
-f dockerfile.metrics \
--build-arg PACKAGE_NAME=metrics \
--build-arg PACKAGE_PREFIX=sidecar/ \
--build-arg VCS_REF=`git rev-parse HEAD` \
--build-arg BUILD_DATE=`date -u +”%Y-%m-%dT%H:%M:%SZ”` \
.
Expand All @@ -26,7 +31,8 @@ tracer:
cd "$$GOPATH/src/github.com/ardanlabs/service"
docker build \
-t tracer-amd64:1.0 \
-f dockerfile.tracer \
--build-arg PACKAGE_NAME=tracer \
--build-arg PACKAGE_PREFIX=sidecar/ \
--build-arg VCS_REF=`git rev-parse HEAD` \
--build-arg BUILD_DATE=`date -u +”%Y-%m-%dT%H:%M:%SZ”` \
.
Expand Down

0 comments on commit 2128d27

Please sign in to comment.