1.1.0 • Published 6 years ago

koa-git-smart-proxy v1.1.0

Weekly downloads
2
License
MIT
Repository
github
Last release
6 years ago

koa-git-smart-proxy

A proxy library for custom git deploy logic made for koa.

Note: The api is currently not fully tested and may be unstable. Working on tests.

Install

npm install --save koa-git-smart-proxy

Why?

Looking at existing git deployment libraries for node, not many have good compatibility with koa.So instead of creating a compatibility layer for my application, I created a new library made for koa. All core functinality now lives in a seperate package.

I took insparation from existing packages for node; pushover, git-http-backend, gems for ruby; grack and the http protocol documentation for git.

API

create( context, command ) (function) (export)

Note: You can also use the static class method GitSmartProxy.middleware.

Create a new GitSmartProxy instance, and wait till it's ready. Return a promise for the instance.

Parameters

Returns

See also

Usage example

Bare usage.

const { spawn } = require('child_process');
const { createServer } = require('http');
const koa =  require('koa');
const { create, ServiceType } = require('koa-git-smart-proxy');
const { resolve } = require('path');

function command(repo_path, command, args = []) {
  const full_path = resolve(r);

  return spawn('git', [command, ...args, '.'], {
    cwd: full_path,
  });
}

const app = new koa;

app.use(async(ctx) => {
  const service = await create(ctx, command);

  // Not found
  if (!await service.exists()) {
    return service.reject(404);
  }

  // Forbidden
  if (service.service === ServiceType.UNKNOWN) {
    return service.reject();
  }

  return service.accept();
});

const server = createServer(app.callback());

server.listen(3000, () => console.log('listening on port 3000'));

middleware( options ) (function) (export)

Note: You can also use the static class method GitSmartProxy.middleware.

Creates a middleware attaching a new instance to context.

Parameters

Returns

See also

Usage examples

Bare usage (with auto deploy).

const { createServer } = require('http');
const koa =  require('koa');
const { middleware } = require('koa-git-smart-proxy');

const {
  GIT_ROOT_FOLDER: root_folder = '/data/repos',
} = process.env;

const app = new koa;

app.use(middleware({
  auto_deploy: true,
  git: root_folder,
}));

const server = createServer(app.callback());

server.listen(3000, () => console.log('listening on port 3000'));

Bare usage (without auto deploy).

const { createServer } = require('http');
const koa =  require('koa');
const { middleware } = require('koa-git-smart-proxy');

const {
  GIT_ROOT_FOLDER: root_folder = '/data/repos',
} = process.env;

const app = new koa;

app.use(middleware({
  git: root_folder,
}));

app.use(async(ctx) => {
  const {proxy} = ctx.state;

  // Not found
  if (!await proxy.exists()) {
    return proxy.reject(404);
  }

  // Forbidden
  if (proxy.service === ServiceType.UNKNOWN) {
    return proxy.reject();
  }

  return proxy.accept();
});

const server = createServer(app.callback());

server.listen(3000, () => console.log('listening on port 3000'));

Throw errors to context (with auto deploy)

const { createServer } = require('http');
const koa =  require('koa');
const { middleware } = require('koa-git-smart-proxy');

const {
  GIT_ROOT_FOLDER: root_folder = '/data/repos',
} = process.env;

const app = new koa;

app.use(async(ctx, next) => {
  try {
    await next();

  // Error __may__ be thrown from middleware
  } catch (err) {
    console.log(err);

    ctx.body = err.message;
    ctx.status = err.status || 500;
  }
})

app.use(middleware({
  throw_errors: true,
  auto_deploy: true,
  git: root_folder,
}));

const server = createServer(app.callback());

server.listen(3000, () => console.log('listening on port 3000'));

GitSmartProxy (class) (export)

Note: When creating new instances, use exported function create or static method create.

Public properties

Signals

  • onAccept \<Signal\> Dispatched when request is accepted. Where T (payload) is accepted path for repository.

  • onReject \<Signal\> Dispatched when request is rejected. Where T (payload) is an object containing properties 'reason' and 'status'.

  • onFinal \<Signal\> Dispatched when request is either accepted or rejected. Has no payload.

  • onError \<Signal\> Dispatched when something goes wrong. Where T (payload) is an instance of Error.

Signals Usage Example

Usage example. (With auto deploy)

/* ... init app and add middleware */

app.use(async(ctx, next) => {
  const {proxy} = ctx.state;

  // Fires only when accepted
  proxy.onAccept.add((repo_path) => console.log('accepted %s', repo_path));

  // Fires only when rejected
  proxy.onReject.add(({reason, status}) =>
    console.log('rejected with reason "%s" and status %s', reason, status));

  // Fires either when rejected or accepted
  proxy.onFinal.add(() => console.log('service is done'));

  // Fires on any error in accept, reject or exists.
  proxy.onError.add((err) => console.error(err));
});

Public static methods

Public instance methods

GitSmartProxy.create( context, command ) (static method)

Note: You can also use the exported create function.

Create a new GitSmartProxy instance, and wait till it's ready. Return a promise for the instance.

Parameters

Returns

See also

Usage example

Bare usage.

const { spawn } = require('child_process');
const { createServer } = require('http');
const koa =  require('koa');
const { GitSmartProxy, ServiceType } = require('koa-git-smart-proxy');
const { resolve } = require('path');

function command(repo_path, command, args = []) {
  const full_path = resolve(r);

  return spawn('git', [command, ...args, '.'], {
    cwd: full_path,
  });
}

const app = new koa;

app.use(async(ctx) => {
  const service = await GitSmartProxy.create(ctx, command);

  // Not found
  if (!await service.exists()) {
    return service.reject(404);
  }

  // Forbidden
  if (service.service === ServiceType.UNKNOWN) {
    return service.reject();
  }

  return service.accept();
});

const server = createServer(app.callback());

server.listen(3000, () => console.log('listening on port 3000'));

GitSmartProxy.middleware( options ) (static method)

Note: You can also use the exported middleware function.

Creates a middleware attaching a new instance to context.

Parameters

Returns

See also

Usage examples

Bare usage (with auto deploy).

const { createServer } = require('http');
const koa =  require('koa');
const { GitSmartProxy } = require('koa-git-smart-proxy');

const {
  GIT_ROOT_FOLDER: root_folder = '/data/repos',
} = process.env;

const app = new koa;

app.use(GitSmartProxy.middleware({
  auto_deploy: true,
  git: root_folder,
}));

const server = createServer(app.callback());

server.listen(3000, () => console.log('listening on port 3000'));

Bare usage (without auto deploy).

const { createServer } = require('http');
const koa =  require('koa');
const { GitSmartProxy } = require('koa-git-smart-proxy');

const app = new koa;

app.use(GitSmartProxy.middleware({
  git: root_folder,
}));

app.use(async(ctx) => {
  const {proxy} = ctx.state;

  // Not found
  if (!await proxy.exists()) {
    return proxy.reject(404);
  }

  // Forbidden
  if (proxy.service === ServiceType.UNKNOWN) {
    return proxy.reject();
  }

  return proxy.accept();
});

const server = createServer(app.callback());

server.listen(3000, () => console.log('listening on port 3000'));

Throw errors to context (with auto deploy)

const { createServer } = require('http');
const koa =  require('koa');
const { GitSmartProxy } = require('koa-git-smart-proxy');

const {
  GIT_ROOT_FOLDER: root_folder = '/data/repos',
} = process.env;

const app = new koa;

app.use(async(ctx, next) => {
  try {
    await next();

  // Error __may__ be thrown from middleware
  } catch (err) {
    console.log(err);

    ctx.body = err.message;
    ctx.status = err.status || 500;
  }
})

app.use(GitSmartProxy.middleware({
  throw_errors: true,
  auto_deploy: true,
  git: root_folder,
}));

const server = createServer(app.callback());

server.listen(3000, () => console.log('listening on port 3000'));

GitSmartProxy#accept( alternative_path ) (instance method)

Accept the request for provided service.

Parameters

Returns

GitSmartProxy#reject( status, ) (instance method)

Reject request to service. Optionally supplied with status code and reason.

Parameters

Returns

GitSmartProxy#reject( reason, status ) (instance method)

Reject request to service. Supplied with a reason and optionally a status code.

Parameters

Returns

GitSmartProxy#exists( alternative_path ) (instance method)

Checks if repository exists.

Parameters

Returns

GitSmartProxy#verbose( ...messages ) (instance method)

Side-loades verbose messages to client.

Parameters

ServiceType (enum) (export)

Service types with values.

Values

  • UNKNOWN (0) Unknown service.

  • INFO (1) Advertise refs.

  • PULL (2) All forms of data fetch.

  • PUSH (3) All forms of data push. (Including setting tags)

RequestStatus (enum) (export)

Request stauts with values.

Values

  • PENDING (0) Request is still pending.

  • ACCEPTED (1) Request was accepted.

  • REJECTED (2) Request was rejected.

GitMetadata (interface) (typescript only export)

Request metadata. Only available for pull/push services.

Properties

MiddlewareOptions (interface) (typescript only export)

Middleware options.

Properties

GitCommand (type) (typescript only export)

A function returning stdin/stdout of a spawned git process.

Parameters

Returns

GitCommandResult (interface) (typescript only export)

An object containing stdin/stdout of a git process.

Properties

GitExecutableOptions (interface) (typescript only export)

Options for customizing the default GitCommand.

Properties

Specialized Usage

Custom authentication/authorization example

In the below example, we statelessly authenticate with HTTP Basic Authenticatoin and check if both repo exist and service is available for user (or guest).

const koa = require('koa');
const passport = require('koa-passport');
const HttpStatus = ('http-status');
const { match } = require('koa-match');
const { middleware, ServiceType } = require('koa-git-smart-proxy');
const Models = require('./models');

// Get repository root folder
const {
  REPOS_ROOT_FOLDER: root_folder = '/data/repos',
} = process.env;

// Create app
const app = new koa;

// Git services (Info, pull & push)
app.use(match({
  path: ':username/:repository.git/:path(.*)?',
  handlers: [
    // Authenticate client
    passport.initialize(),
    passport.authenticate('basic'),

    // Get repository
    async(ctx, next) => {
      const {username, repository} = ctx.params;

      const repo = await Models.Repository.findByOwnerAndName(username, repository);

      if (repo) {
        ctx.state.repo = repo;
      }

      return next();
    },

    // Attach proxy
    middleware({git: {root_folder} }),

    // Validation
    async(ctx) => {
      const {proxy, repo, user} = ctx.state;
      const {username, repository, path} = ctx.params;

      // Redirect
      if (!path) {
        return ctx.redirect('back', `/${username}/${repository}/`);
      }

      // Not found
      if (!repo) {
        return proxy.reject(HttpStatus.NOT_FOUND);
      }

      // Unknown service
      if (proxy.service === ServiceType.UNKNOWN) {
        return proxy.reject();
      }

      // Unautrorized access
      if (!await repo.checkService(proxy.service, user)) {
        ctx.set('www-authenticate', `Basic`);
        return proxy.reject(HttpStatus.UNAUTHORIZED);
      }

      // Repos are stored differently than url structure.
      const repo_path = repo.get_path();

      // #accept can be supplied with an absolute path
      // or a path relative to root_folder.
      return proxy.accept(repo_path);
    }
  ]
}));

const server = createServer(app.callback());

server.listen(3000, () => console.log('listening on port 3000'));

Typescript

This module includes a TypeScript declaration file to enable auto complete in compatible editors and type information for TypeScript projects. This module depends on the Node.js types, so install @types/node:

npm install --save-dev @types/node

License

MIT