Rethinking Packaging, Development and Deployment 



Domen Kožar / @iElectric

Nix / Python / JavaScript freelancer


EuroPython 2014

Core issues with packaging in Python



  • dynamic packaging metadata: setup.py
    • upcoming PEP-0426

  • legacy infrastructure tools: distutils/setuptools
    • PyPA

  • non-Python dependencies
    • ???

Best tool for the job?

JavaScript stack is inevitable in 2014

Nix project

  • minimalistic purely functional language (configuration syntax + lambda functions)
  • lazy evaluated, dynamically typed
  • runs on POSIX (Linux / OS X / FreeBSD)
  • (could work on Windows, if there was enough interest)
  • solves dependency hell set of problems
  • developed by Eelco Dolstra as part of his PhD thesis in 2006, Utrecht, The Netherlands
  • 11 years old project

Purely functional language

  • Software package is an output of a function that is deterministic (it depends only on function inputs, without any side effects)


/nix/store/r8vvq9kq18pz08v249h8my6r9vs7s0n3-nginx-1.5.13/


  • /nix/store is immutable (mounted read-only)
  • r8vvq9kq18pz08v249h8my6r9vs7s0n3 is crypto hash of function inputs

nginx package

{ stdenv, fetchurl, openssl, zlib, pcre, libxml2, libxslt, expat }:

stdenv.mkDerivation rec {
  name = "nginx-${version}";
  version = "1.5.13";

  src = fetchurl {
    url = "http://nginx.org/download/nginx-${version}.tar.gz";
    sha256 = "1f82845mpgmhvm151fhn2cnqjggw9w7cvsqbva9rb320wmc9m63w";
  };

  buildInputs = [ openssl zlib pcre libxml2 libxslt ];
  configureFlags = [ "--with-http_spdy_module" ];

  meta = with stdenv.lib; {
    description = "A reverse proxy and lightweight webserver";
    maintainers = [ maintainers.iElectric ];
    platforms = platforms.all;
    license = licenses.bsd2;
  };
}

Lazy (extensible)


      Override lambda function parameters:
myNginx = pkgs.nginx.override { openssl = openssl101h; }

      Override derivation attributes:
myNginx = pkgs.lib.overrideDerivation pkgs.nginx (args:
  src = fetchgit { ... }; 
})


Installing Nix

$ bash <(curl https://nixos.org/nix/install)
$ source ~/.nix-profile/etc/profile.d/nix.sh


Uninstalling Nix

$ rm -rf /nix
$ rm -rf ~/nix-profile/

 

Profiles / User Environment

 

Demo

Packaging Python software with Nix

{ fetchurl, buildPythonPackage }:

buildPythonPackage rec {
  name = "Pillow-2.3.0";

  src = fetchurl {
    url = "http://pypi.python.org/packages/source/P/Pillow/${name}.zip";
    md5 = "56b6614499aacb7d6b5983c4914daea7";
  };

  buildInputs = [ pkgs.freetype pkgs.libjpeg pkgs.zlib pkgs.libtiff pkgs.libwebp ];
 
  meta = with stdenv.lib; {
    homepage = http://python-imaging.github.com/Pillow;
    description = "Fork of The Python Imaging Library (PIL)";
    license = "http://www.pythonware.com/products/pil/license.htm";
    maintainers = [ maintainers.iElectric ];
  };
};

Ways to package Python packages for Nix


  • manually
    • cumbersome, if you have lots of packages
  • python2nix
    • spits out simple templates
  • pypi2nix
    • tries to handle all edge cases

nix-shell

(virtualenv, but for all software packages)



Using nix-shell to provide tools

$ cat default.nix
with import <nixpkgs> {};
with pkgs.python27Packages; 

buildPythonPackage {
  name = "some-env";

  buildInputs = [ virtualenv mysql psycopg2 pillow git ];

  src = null;
}

Activate nix-shell:

$ nix-shell

Don't preserve current bash environment:

$ nix-shell --pure

Demo

 

development of
a Python package demo

NixOS

{
  boot.loader.grub.device = "/dev/sda";
  fileSystems."/".device = "/dev/sda1";

  networking.firewall = {
    enable = true;
    allowedTCPPorts = [ 80 ];
  };

  environment.systemPackages = with pkgs; [
    wget
    git
    gnupg
    tmux
  ];
  
  services = {
    sshd.enable = true;

    munin-node.enable = true;
    munin-cron = {
      enable = true;
      hosts = ''
        [${config.networking.hostName}]
        address localhost
      '';
    };
  };
}

Deploying Pyramid with NixOS

let 
  myproject = import ../default.nix {};
  productionini = pkgs.writeText "myproject-production.ini" ''
    ...
  '';
in {
  systemd.services.myproject = {
    after = [ "network.target" ];
    description = "My Project";
    wantedBy = [ "multi-user.target" ];
    path = [ myproject ];
    environment.PYTHONPATH = "${myproject}/lib/python2.7/site-packages";
    serviceConfig = {
      ExecStart = "${pkgs.pythonPackages.pyramid}/bin/pserve ${productionini}";
      User = "myuser";
      Group = "myuser";
    };
  };
}

Getting started with NixOps

$ nix-env -i nixops

trivial.nix:

{
  webserver = { config, pkgs, ... }:
    { services.httpd.enable = true;
      services.httpd.adminAddr = "alice@example.org";
      services.httpd.documentRoot = "${pkgs.valgrind}/share/doc/valgrind/html";
    };
}

trivial-virtualbox.nix:

{
  webserver = { config, pkgs, ... }:
    { deployment.targetEnv = "virtualbox";
      deployment.virtualbox.memorySize = 1024; # megabytes
    };
}

Provision and deploy

$ nixops create -d trivial ./trivial.nix ./trivial-virtualbox.nix 33bced96-5f26-11e1-b9d7-9630d48abec1

$ nixops deploy -d trivial
creating VirtualBox VM ‘webserver’...

Demo

NixOS Sprint @ Ljubljana, Slovenia


23rd to 27th of August 2014


Enjoy the purity! Questions?


  • #nixos on Freenode
  • http://nixos.org

(Shameless plugs)

Rethinking Packaging, Development and Deployment

By Domen Kožar

Rethinking Packaging, Development and Deployment

  • 1,802