github.com/pachyderm/pachyderm@v1.13.4/src/server/pkg/deploy/assets/assets.go (about)

     1  package assets
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"path"
     7  	"path/filepath"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/pachyderm/pachyderm/src/client"
    12  	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
    13  	"github.com/pachyderm/pachyderm/src/client/pkg/tls"
    14  	auth "github.com/pachyderm/pachyderm/src/server/auth/server"
    15  	pfs "github.com/pachyderm/pachyderm/src/server/pfs/server"
    16  	"github.com/pachyderm/pachyderm/src/server/pkg/obj"
    17  	"github.com/pachyderm/pachyderm/src/server/pkg/serde"
    18  	"github.com/pachyderm/pachyderm/src/server/pkg/uuid"
    19  	"github.com/pachyderm/pachyderm/src/server/pps/server/githook"
    20  
    21  	apps "k8s.io/api/apps/v1"
    22  	v1 "k8s.io/api/core/v1"
    23  	rbacv1 "k8s.io/api/rbac/v1"
    24  	"k8s.io/apimachinery/pkg/api/resource"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/util/intstr"
    27  )
    28  
    29  const (
    30  	// WorkerServiceAccountEnvVar is the name of the environment variable used to tell pachd
    31  	// what service account to assign to new worker RCs, for the purpose of
    32  	// creating S3 gateway services.
    33  	WorkerServiceAccountEnvVar = "WORKER_SERVICE_ACCOUNT"
    34  	// DefaultWorkerServiceAccountName is the default value to use if WorkerServiceAccountEnvVar is
    35  	// undefined (for compatibility purposes)
    36  	DefaultWorkerServiceAccountName = "pachyderm-worker"
    37  	workerRoleName                  = "pachyderm-worker" // Role given to worker Service Account
    38  	workerRoleBindingName           = "pachyderm-worker" // Binding worker role to Service Account
    39  )
    40  
    41  var (
    42  	suite = "pachyderm"
    43  
    44  	pachdImage = "pachyderm/pachd"
    45  	// Using our own etcd image for now because there's a fix we need
    46  	// that hasn't been released, and which has been manually applied
    47  	// to the official v3.2.7 release.
    48  	etcdImage      = "pachyderm/etcd:v3.3.5"
    49  	postgresImage  = "postgres:13.0-alpine"
    50  	grpcProxyImage = "pachyderm/grpc-proxy:0.4.10"
    51  	dashName       = "dash"
    52  	workerImage    = "pachyderm/worker"
    53  	pauseImage     = "gcr.io/google_containers/pause-amd64:3.0"
    54  
    55  	// ServiceAccountName is the name of Pachyderm's service account.
    56  	// It's public because it's needed by pps.APIServer to create the RCs for
    57  	// workers.
    58  	ServiceAccountName      = "pachyderm"
    59  	etcdHeadlessServiceName = "etcd-headless"
    60  	etcdName                = "etcd"
    61  	etcdVolumeName          = "etcd-volume"
    62  	etcdVolumeClaimName     = "etcd-storage"
    63  	// The storage class name to use when creating a new StorageClass for etcd.
    64  	defaultEtcdStorageClassName = "etcd-storage-class"
    65  	grpcProxyName               = "grpc-proxy"
    66  	pachdName                   = "pachd"
    67  	// PrometheusPort hosts the prometheus stats for scraping
    68  	PrometheusPort = 656
    69  
    70  	postgresName                    = "postgres"
    71  	postgresVolumeName              = "postgres-volume"
    72  	postgresVolumeClaimName         = "postgres-storage"
    73  	defaultPostgresStorageClassName = "postgres-storage-class"
    74  
    75  	// Role & binding names, used for Roles or ClusterRoles and their associated
    76  	// bindings.
    77  	roleName        = "pachyderm"
    78  	roleBindingName = "pachyderm"
    79  	// Policy rules to use for Pachyderm's Role or ClusterRole.
    80  	rolePolicyRules = []rbacv1.PolicyRule{{
    81  		APIGroups: []string{""},
    82  		Verbs:     []string{"get", "list", "watch"},
    83  		Resources: []string{"nodes", "pods", "pods/log", "endpoints"},
    84  	}, {
    85  		APIGroups: []string{""},
    86  		Verbs:     []string{"get", "list", "watch", "create", "update", "delete"},
    87  		Resources: []string{"replicationcontrollers", "services", "replicationcontrollers/scale"},
    88  	}, {
    89  		APIGroups: []string{""},
    90  		Verbs:     []string{"get", "list", "watch", "create", "update", "delete", "deletecollection"},
    91  		Resources: []string{"secrets"},
    92  	}}
    93  
    94  	// The name of the local volume (mounted kubernetes secret) where pachd
    95  	// should read a TLS cert and private key for authenticating with clients
    96  	tlsVolumeName = "pachd-tls-cert"
    97  	// The name of the kubernetes secret mount in the TLS volume (see
    98  	// tlsVolumeName)
    99  	tlsSecretName = "pachd-tls-cert"
   100  
   101  	// Cmd used to launch etcd
   102  	etcdCmd = []string{
   103  		"/usr/local/bin/etcd",
   104  		"--listen-client-urls=http://0.0.0.0:2379",
   105  		"--advertise-client-urls=http://0.0.0.0:2379",
   106  		"--data-dir=/var/data/etcd",
   107  		"--auto-compaction-retention=1",
   108  		"--max-txn-ops=10000",
   109  		"--max-request-bytes=52428800",     //50mb
   110  		"--quota-backend-bytes=8589934592", //8gb
   111  	}
   112  
   113  	// IAMAnnotation is the annotation used for the IAM role, this can work
   114  	// with something like kube2iam as an alternative way to provide
   115  	// credentials.
   116  	IAMAnnotation = "iam.amazonaws.com/role"
   117  )
   118  
   119  // Backend is the type used to enumerate what system provides object storage or
   120  // persistent disks for the cluster (each can be configured separately).
   121  type Backend int
   122  
   123  const (
   124  	// LocalBackend is used in development (e.g. minikube) which provides a volume on the host machine
   125  	LocalBackend Backend = iota
   126  
   127  	// AmazonBackend uses S3 for object storage
   128  	AmazonBackend
   129  
   130  	// GoogleBackend uses GCS for object storage
   131  	GoogleBackend
   132  
   133  	// MicrosoftBackend uses Azure blobs for object storage
   134  	MicrosoftBackend
   135  
   136  	// MinioBackend uses the Minio client for object storage, but it can point to any S3-compatible API
   137  	MinioBackend
   138  
   139  	// S3CustomArgs uses the S3 or Minio clients for object storage with custom endpoint configuration
   140  	S3CustomArgs = 6
   141  )
   142  
   143  // TLSOpts indicates the cert and key file that Pachd should use to
   144  // authenticate with clients
   145  type TLSOpts struct {
   146  	ServerCert string
   147  	ServerKey  string
   148  }
   149  
   150  // FeatureFlags are flags for experimental features.
   151  type FeatureFlags struct {
   152  	// StorageV2, if true, will make Pachyderm use the new storage layer.
   153  	StorageV2 bool
   154  }
   155  
   156  const (
   157  	// UploadConcurrencyLimitEnvVar is the environment variable for the upload concurrency limit.
   158  	UploadConcurrencyLimitEnvVar = "STORAGE_UPLOAD_CONCURRENCY_LIMIT"
   159  
   160  	// PutFileConcurrencyLimitEnvVar is the environment variable for the PutFile concurrency limit.
   161  	PutFileConcurrencyLimitEnvVar = "STORAGE_PUT_FILE_CONCURRENCY_LIMIT"
   162  
   163  	// StorageV2EnvVar is the environment variable for enabling V2 storage.
   164  	StorageV2EnvVar = "STORAGE_V2"
   165  )
   166  
   167  const (
   168  	// DefaultUploadConcurrencyLimit is the default maximum number of concurrent object storage uploads.
   169  	// (bryce) this default is set here and in the service env config, need to figure out how to refactor
   170  	// this to be in one place.
   171  	DefaultUploadConcurrencyLimit = 100
   172  
   173  	// DefaultPutFileConcurrencyLimit is the default maximum number of concurrent files that can be uploaded over GRPC or downloaded from external sources (ex. HTTP or blob storage).
   174  	DefaultPutFileConcurrencyLimit = 100
   175  )
   176  
   177  // StorageOpts are options that are applicable to the storage layer.
   178  type StorageOpts struct {
   179  	UploadConcurrencyLimit  int
   180  	PutFileConcurrencyLimit int
   181  }
   182  
   183  const (
   184  	// RequireCriticalServersOnlyEnvVar is the environment variable for requiring critical servers only.
   185  	RequireCriticalServersOnlyEnvVar = "REQUIRE_CRITICAL_SERVERS_ONLY"
   186  )
   187  
   188  const (
   189  	// DefaultRequireCriticalServersOnly is the default for requiring critical servers only.
   190  	// (bryce) this default is set here and in the service env config, need to figure out how to refactor
   191  	// this to be in one place.
   192  	DefaultRequireCriticalServersOnly = false
   193  )
   194  
   195  // AssetOpts are options that are applicable to all the asset types.
   196  type AssetOpts struct {
   197  	FeatureFlags
   198  	StorageOpts
   199  	PachdShards    uint64
   200  	Version        string
   201  	LogLevel       string
   202  	Metrics        bool
   203  	Dynamic        bool
   204  	EtcdNodes      int
   205  	EtcdVolume     string
   206  	PostgresNodes  int
   207  	PostgresVolume string
   208  	DashOnly       bool
   209  	NoDash         bool
   210  	DashImage      string
   211  	Registry       string
   212  	EtcdPrefix     string
   213  	PachdPort      int32
   214  	TracePort      int32
   215  	HTTPPort       int32
   216  	PeerPort       int32
   217  
   218  	// NoGuaranteed will not generate assets that have both resource limits and
   219  	// resource requests set which causes kubernetes to give the pods
   220  	// guaranteed QoS. Guaranteed QoS generally leads to more stable clusters
   221  	// but on smaller test clusters such as those run on minikube it doesn't
   222  	// help much and may cause more instability than it prevents.
   223  	NoGuaranteed bool
   224  
   225  	// DisableAuthentication stops Pachyderm's authentication service
   226  	// from talking to GitHub, for testing. Instead users can authenticate
   227  	// simply by providing a username.
   228  	DisableAuthentication bool
   229  
   230  	// BlockCacheSize is the amount of memory each PachD node allocates towards
   231  	// its cache of PFS blocks. If empty, assets.go will choose a default size.
   232  	BlockCacheSize string
   233  
   234  	// PachdCPURequest is the amount of CPU we request for each pachd node. If
   235  	// empty, assets.go will choose a default size.
   236  	PachdCPURequest string
   237  
   238  	// PachdNonCacheMemRequest is the amount of memory we request for each
   239  	// pachd node in addition to BlockCacheSize. If empty, assets.go will choose
   240  	// a default size.
   241  	PachdNonCacheMemRequest string
   242  
   243  	// EtcdCPURequest is the amount of CPU (in cores) we request for each etcd
   244  	// node. If empty, assets.go will choose a default size.
   245  	EtcdCPURequest string
   246  
   247  	// EtcdMemRequest is the amount of memory we request for each etcd node. If
   248  	// empty, assets.go will choose a default size.
   249  	EtcdMemRequest string
   250  
   251  	// EtcdStorageClassName is the name of an existing StorageClass to use when
   252  	// creating a StatefulSet for dynamic etcd storage. If unset, a new
   253  	// StorageClass will be created for the StatefulSet.
   254  	EtcdStorageClassName string
   255  
   256  	// PostgresCPURequest is the amount of CPU (in cores) we request for each
   257  	// postgres node. If empty, assets.go will choose a default size.
   258  	PostgresCPURequest string
   259  
   260  	// PostgresMemRequest is the amount of memory we request for each postgres
   261  	// node. If empty, assets.go will choose a default size.
   262  	PostgresMemRequest string
   263  
   264  	// PostgresStorageClassName is the name of an existing StorageClass to use when
   265  	// creating a StatefulSet for dynamic postgres storage. If unset, a new
   266  	// StorageClass will be created for the StatefulSet.
   267  	PostgresStorageClassName string
   268  
   269  	// IAM role that the Pachyderm deployment should assume when talking to AWS
   270  	// services (if using kube2iam + metadata service + IAM role to delegate
   271  	// permissions to pachd via its instance).
   272  	// This is in AssetOpts rather than AmazonCreds because it must be passed
   273  	// as an annotation on the pachd pod rather than as a k8s secret
   274  	IAMRole string
   275  
   276  	// ImagePullSecret specifies an image pull secret that gets attached to the
   277  	// various deployments so that their images can be pulled from a private
   278  	// registry.
   279  	ImagePullSecret string
   280  
   281  	// NoRBAC, if true, will disable creation of RBAC assets.
   282  	NoRBAC bool
   283  
   284  	// LocalRoles, if true, uses Role and RoleBinding instead of ClusterRole and
   285  	// ClusterRoleBinding.
   286  	LocalRoles bool
   287  
   288  	// Namespace is the kubernetes namespace to deploy to.
   289  	Namespace string
   290  
   291  	// NoExposeDockerSocket if true prevents pipelines from accessing the docker socket.
   292  	NoExposeDockerSocket bool
   293  
   294  	// ExposeObjectAPI, if set, causes pachd to serve Object/Block API requests on
   295  	// its public port. This should generally be false in production (it breaks
   296  	// auth) but is needed by tests
   297  	ExposeObjectAPI bool
   298  
   299  	// If set, the files indictated by 'TLS.ServerCert' and 'TLS.ServerKey' are
   300  	// placed into a Kubernetes secret and used by pachd nodes to authenticate
   301  	// during TLS
   302  	TLS *TLSOpts
   303  
   304  	// Sets the cluster deployment ID. If unset, this will be a randomly
   305  	// generated UUID without dashes.
   306  	ClusterDeploymentID string
   307  
   308  	// RequireCriticalServersOnly is true when only the critical Pachd servers
   309  	// are required to startup and run without error.
   310  	RequireCriticalServersOnly bool
   311  
   312  	// WorkerServiceAccountName is the name of the service account that will be
   313  	// used in the worker pods for creating S3 gateways.
   314  	WorkerServiceAccountName string
   315  }
   316  
   317  // replicas lets us create a pointer to a non-zero int32 in-line. This is
   318  // helpful because the Replicas field of many specs expectes an *int32
   319  func replicas(r int32) *int32 {
   320  	return &r
   321  }
   322  
   323  // fillDefaultResourceRequests sets any of:
   324  //   opts.BlockCacheSize
   325  //   opts.PachdNonCacheMemRequest
   326  //   opts.PachdCPURequest
   327  //   opts.EtcdCPURequest
   328  //   opts.EtcdMemRequest
   329  // that are unset in 'opts' to the appropriate default ('persistentDiskBackend'
   330  // just used to determine if this is a local deployment, and if so, make the
   331  // resource requests smaller)
   332  func fillDefaultResourceRequests(opts *AssetOpts, persistentDiskBackend Backend) {
   333  	if persistentDiskBackend == LocalBackend {
   334  		// For local deployments, we set the resource requirements and cache sizes
   335  		// low so that pachyderm clusters will fit inside e.g. minikube or travis
   336  		if opts.BlockCacheSize == "" {
   337  			opts.BlockCacheSize = "256M"
   338  		}
   339  		if opts.PachdNonCacheMemRequest == "" {
   340  			opts.PachdNonCacheMemRequest = "256M"
   341  		}
   342  		if opts.PachdCPURequest == "" {
   343  			opts.PachdCPURequest = "0.25"
   344  		}
   345  
   346  		if opts.EtcdMemRequest == "" {
   347  			opts.EtcdMemRequest = "512M"
   348  		}
   349  		if opts.EtcdCPURequest == "" {
   350  			opts.EtcdCPURequest = "0.25"
   351  		}
   352  
   353  		if opts.PostgresMemRequest == "" {
   354  			opts.PostgresMemRequest = "256M"
   355  		}
   356  		if opts.PostgresCPURequest == "" {
   357  			opts.PostgresCPURequest = "0.25"
   358  		}
   359  	} else {
   360  		// For non-local deployments, we set the resource requirements and cache
   361  		// sizes higher, so that the cluster is stable and performant
   362  		if opts.BlockCacheSize == "" {
   363  			opts.BlockCacheSize = "1G"
   364  		}
   365  		if opts.PachdNonCacheMemRequest == "" {
   366  			opts.PachdNonCacheMemRequest = "2G"
   367  		}
   368  		if opts.PachdCPURequest == "" {
   369  			opts.PachdCPURequest = "1"
   370  		}
   371  
   372  		if opts.EtcdMemRequest == "" {
   373  			opts.EtcdMemRequest = "2G"
   374  		}
   375  		if opts.EtcdCPURequest == "" {
   376  			opts.EtcdCPURequest = "1"
   377  		}
   378  
   379  		if opts.PostgresMemRequest == "" {
   380  			opts.PostgresMemRequest = "2G"
   381  		}
   382  		if opts.PostgresCPURequest == "" {
   383  			opts.PostgresCPURequest = "1"
   384  		}
   385  	}
   386  }
   387  
   388  // ServiceAccounts returns a kubernetes service account for use with Pachyderm.
   389  func ServiceAccounts(opts *AssetOpts) []*v1.ServiceAccount {
   390  	return []*v1.ServiceAccount{
   391  		// Pachd service account
   392  		{
   393  			TypeMeta: metav1.TypeMeta{
   394  				Kind:       "ServiceAccount",
   395  				APIVersion: "v1",
   396  			},
   397  			ObjectMeta: objectMeta(ServiceAccountName, labels(""), nil, opts.Namespace),
   398  		},
   399  
   400  		// worker service account
   401  		{
   402  			TypeMeta: metav1.TypeMeta{
   403  				Kind:       "ServiceAccount",
   404  				APIVersion: "v1",
   405  			},
   406  			ObjectMeta: objectMeta(opts.WorkerServiceAccountName, labels(""), nil, opts.Namespace),
   407  		},
   408  	}
   409  }
   410  
   411  // ClusterRole returns a ClusterRole that should be bound to the Pachyderm service account.
   412  func ClusterRole(opts *AssetOpts) *rbacv1.ClusterRole {
   413  	return &rbacv1.ClusterRole{
   414  		TypeMeta: metav1.TypeMeta{
   415  			Kind:       "ClusterRole",
   416  			APIVersion: "rbac.authorization.k8s.io/v1",
   417  		},
   418  		ObjectMeta: objectMeta(roleName, labels(""), nil, opts.Namespace),
   419  		Rules:      rolePolicyRules,
   420  	}
   421  }
   422  
   423  // ClusterRoleBinding returns a ClusterRoleBinding that binds Pachyderm's
   424  // ClusterRole to its ServiceAccount.
   425  func ClusterRoleBinding(opts *AssetOpts) *rbacv1.ClusterRoleBinding {
   426  	// cluster role bindings are global to the cluster and not associated with a namespace,
   427  	// so if pachyderm is deployed multiple times in different namespaces, each will try to
   428  	// create its own cluster role binding. To avoid interference, include namespace in the name
   429  	scopedName := fmt.Sprintf("%s-%s", roleBindingName, opts.Namespace)
   430  	return &rbacv1.ClusterRoleBinding{
   431  		TypeMeta: metav1.TypeMeta{
   432  			Kind:       "ClusterRoleBinding",
   433  			APIVersion: "rbac.authorization.k8s.io/v1",
   434  		},
   435  		ObjectMeta: objectMeta(scopedName, labels(""), nil, opts.Namespace),
   436  		Subjects: []rbacv1.Subject{{
   437  			Kind:      "ServiceAccount",
   438  			Name:      ServiceAccountName,
   439  			Namespace: opts.Namespace,
   440  		}},
   441  		RoleRef: rbacv1.RoleRef{
   442  			Kind: "ClusterRole",
   443  			Name: roleName,
   444  		},
   445  	}
   446  }
   447  
   448  // Role returns a Role that should be bound to the Pachyderm service account.
   449  func Role(opts *AssetOpts) *rbacv1.Role {
   450  	return &rbacv1.Role{
   451  		TypeMeta: metav1.TypeMeta{
   452  			Kind:       "Role",
   453  			APIVersion: "rbac.authorization.k8s.io/v1",
   454  		},
   455  		ObjectMeta: objectMeta(roleName, labels(""), nil, opts.Namespace),
   456  		Rules:      rolePolicyRules,
   457  	}
   458  }
   459  
   460  // workerRole returns a Role bound to the Pachyderm worker service account
   461  // (used by workers to create an s3 gateway k8s service for each job)
   462  func workerRole(opts *AssetOpts) *rbacv1.Role {
   463  	return &rbacv1.Role{
   464  		TypeMeta: metav1.TypeMeta{
   465  			Kind:       "Role",
   466  			APIVersion: "rbac.authorization.k8s.io/v1",
   467  		},
   468  		ObjectMeta: objectMeta(workerRoleName, labels(""), nil, opts.Namespace),
   469  		Rules: []rbacv1.PolicyRule{{
   470  			APIGroups: []string{""},
   471  			Verbs:     []string{"get", "list", "update", "create", "delete"},
   472  			Resources: []string{"services"},
   473  		}},
   474  	}
   475  }
   476  
   477  // RoleBinding returns a RoleBinding that binds Pachyderm's Role to its
   478  // ServiceAccount.
   479  func RoleBinding(opts *AssetOpts) *rbacv1.RoleBinding {
   480  	return &rbacv1.RoleBinding{
   481  		TypeMeta: metav1.TypeMeta{
   482  			Kind:       "RoleBinding",
   483  			APIVersion: "rbac.authorization.k8s.io/v1",
   484  		},
   485  		ObjectMeta: objectMeta(roleBindingName, labels(""), nil, opts.Namespace),
   486  		Subjects: []rbacv1.Subject{{
   487  			Kind:      "ServiceAccount",
   488  			Name:      ServiceAccountName,
   489  			Namespace: opts.Namespace,
   490  		}},
   491  		RoleRef: rbacv1.RoleRef{
   492  			Kind: "Role",
   493  			Name: roleName,
   494  		},
   495  	}
   496  }
   497  
   498  // RoleBinding returns a RoleBinding that binds Pachyderm's workerRole to its
   499  // worker service account (assets.WorkerServiceAccountName)
   500  func workerRoleBinding(opts *AssetOpts) *rbacv1.RoleBinding {
   501  	return &rbacv1.RoleBinding{
   502  		TypeMeta: metav1.TypeMeta{
   503  			Kind:       "RoleBinding",
   504  			APIVersion: "rbac.authorization.k8s.io/v1",
   505  		},
   506  		ObjectMeta: objectMeta(workerRoleBindingName, labels(""), nil, opts.Namespace),
   507  		Subjects: []rbacv1.Subject{{
   508  			Kind:      "ServiceAccount",
   509  			Name:      opts.WorkerServiceAccountName,
   510  			Namespace: opts.Namespace,
   511  		}},
   512  		RoleRef: rbacv1.RoleRef{
   513  			Kind: "Role",
   514  			Name: workerRoleName,
   515  		},
   516  	}
   517  }
   518  
   519  // GetBackendSecretVolumeAndMount returns a properly configured Volume and
   520  // VolumeMount object given a backend.  The backend needs to be one of the
   521  // constants defined in pfs/server.
   522  func GetBackendSecretVolumeAndMount(backend string) (v1.Volume, v1.VolumeMount) {
   523  	return v1.Volume{
   524  			Name: client.StorageSecretName,
   525  			VolumeSource: v1.VolumeSource{
   526  				Secret: &v1.SecretVolumeSource{
   527  					SecretName: client.StorageSecretName,
   528  				},
   529  			},
   530  		}, v1.VolumeMount{
   531  			Name:      client.StorageSecretName,
   532  			MountPath: "/" + client.StorageSecretName,
   533  		}
   534  }
   535  
   536  // GetSecretEnvVars returns the environment variable specs for the storage secret.
   537  func GetSecretEnvVars(storageBackend string) []v1.EnvVar {
   538  	var envVars []v1.EnvVar
   539  	if storageBackend != "" {
   540  		envVars = append(envVars, v1.EnvVar{
   541  			Name:  obj.StorageBackendEnvVar,
   542  			Value: storageBackend,
   543  		})
   544  	}
   545  	trueVal := true
   546  	for _, e := range obj.EnvVarToSecretKey {
   547  		envVars = append(envVars, v1.EnvVar{
   548  			Name: e.Key,
   549  			ValueFrom: &v1.EnvVarSource{
   550  				SecretKeyRef: &v1.SecretKeySelector{
   551  					LocalObjectReference: v1.LocalObjectReference{
   552  						Name: client.StorageSecretName,
   553  					},
   554  					Key:      e.Value,
   555  					Optional: &trueVal,
   556  				},
   557  			},
   558  		})
   559  	}
   560  	return envVars
   561  }
   562  
   563  func getStorageEnvVars(opts *AssetOpts) []v1.EnvVar {
   564  	return []v1.EnvVar{
   565  		{Name: UploadConcurrencyLimitEnvVar, Value: strconv.Itoa(opts.StorageOpts.UploadConcurrencyLimit)},
   566  		{Name: PutFileConcurrencyLimitEnvVar, Value: strconv.Itoa(opts.StorageOpts.PutFileConcurrencyLimit)},
   567  		{Name: StorageV2EnvVar, Value: strconv.FormatBool(opts.FeatureFlags.StorageV2)},
   568  	}
   569  }
   570  
   571  func versionedPachdImage(opts *AssetOpts) string {
   572  	if opts.Version != "" {
   573  		return fmt.Sprintf("%s:%s", pachdImage, opts.Version)
   574  	}
   575  	return pachdImage
   576  }
   577  
   578  func versionedWorkerImage(opts *AssetOpts) string {
   579  	if opts.Version != "" {
   580  		return fmt.Sprintf("%s:%s", workerImage, opts.Version)
   581  	}
   582  	return workerImage
   583  }
   584  
   585  func imagePullSecrets(opts *AssetOpts) []v1.LocalObjectReference {
   586  	var result []v1.LocalObjectReference
   587  	if opts.ImagePullSecret != "" {
   588  		result = append(result, v1.LocalObjectReference{Name: opts.ImagePullSecret})
   589  	}
   590  	return result
   591  }
   592  
   593  // PachdDeployment returns a pachd k8s Deployment.
   594  func PachdDeployment(opts *AssetOpts, objectStoreBackend Backend, hostPath string) *apps.Deployment {
   595  	// set port defaults
   596  	if opts.PachdPort == 0 {
   597  		opts.PachdPort = 650
   598  	}
   599  	if opts.TracePort == 0 {
   600  		opts.TracePort = 651
   601  	}
   602  	if opts.HTTPPort == 0 {
   603  		opts.HTTPPort = 652
   604  	}
   605  	if opts.PeerPort == 0 {
   606  		opts.PeerPort = 653
   607  	}
   608  	mem := resource.MustParse(opts.BlockCacheSize)
   609  	mem.Add(resource.MustParse(opts.PachdNonCacheMemRequest))
   610  	cpu := resource.MustParse(opts.PachdCPURequest)
   611  	image := AddRegistry(opts.Registry, versionedPachdImage(opts))
   612  	volumes := []v1.Volume{
   613  		{
   614  			Name: "pach-disk",
   615  		},
   616  	}
   617  	volumeMounts := []v1.VolumeMount{
   618  		{
   619  			Name:      "pach-disk",
   620  			MountPath: "/pach",
   621  		},
   622  	}
   623  
   624  	// Set up storage options
   625  	var backendEnvVar string
   626  	var storageHostPath string
   627  	switch objectStoreBackend {
   628  	case LocalBackend:
   629  		storageHostPath = path.Join(hostPath, "pachd")
   630  		pathType := v1.HostPathDirectoryOrCreate
   631  		volumes[0].HostPath = &v1.HostPathVolumeSource{
   632  			Path: storageHostPath,
   633  			Type: &pathType,
   634  		}
   635  		backendEnvVar = pfs.LocalBackendEnvVar
   636  	case MinioBackend:
   637  		backendEnvVar = pfs.MinioBackendEnvVar
   638  	case AmazonBackend:
   639  		backendEnvVar = pfs.AmazonBackendEnvVar
   640  	case GoogleBackend:
   641  		backendEnvVar = pfs.GoogleBackendEnvVar
   642  	case MicrosoftBackend:
   643  		backendEnvVar = pfs.MicrosoftBackendEnvVar
   644  	}
   645  	volume, mount := GetBackendSecretVolumeAndMount(backendEnvVar)
   646  	volumes = append(volumes, volume)
   647  	volumeMounts = append(volumeMounts, mount)
   648  	if opts.TLS != nil {
   649  		volumes = append(volumes, v1.Volume{
   650  			Name: tlsVolumeName,
   651  			VolumeSource: v1.VolumeSource{
   652  				Secret: &v1.SecretVolumeSource{
   653  					SecretName: tlsSecretName,
   654  				},
   655  			},
   656  		})
   657  		volumeMounts = append(volumeMounts, v1.VolumeMount{
   658  			Name:      tlsVolumeName,
   659  			MountPath: tls.VolumePath,
   660  		})
   661  	}
   662  	resourceRequirements := v1.ResourceRequirements{
   663  		Requests: v1.ResourceList{
   664  			v1.ResourceCPU:    cpu,
   665  			v1.ResourceMemory: mem,
   666  		},
   667  	}
   668  	if !opts.NoGuaranteed {
   669  		resourceRequirements.Limits = v1.ResourceList{
   670  			v1.ResourceCPU:    cpu,
   671  			v1.ResourceMemory: mem,
   672  		}
   673  	}
   674  	if opts.ClusterDeploymentID == "" {
   675  		opts.ClusterDeploymentID = uuid.NewWithoutDashes()
   676  	}
   677  	envVars := []v1.EnvVar{
   678  		{Name: "PACH_ROOT", Value: "/pach"},
   679  		{Name: "ETCD_PREFIX", Value: opts.EtcdPrefix},
   680  		{Name: "NUM_SHARDS", Value: fmt.Sprintf("%d", opts.PachdShards)},
   681  		{Name: "STORAGE_BACKEND", Value: backendEnvVar},
   682  		{Name: "STORAGE_HOST_PATH", Value: storageHostPath},
   683  		{Name: "WORKER_IMAGE", Value: AddRegistry(opts.Registry, versionedWorkerImage(opts))},
   684  		{Name: "IMAGE_PULL_SECRET", Value: opts.ImagePullSecret},
   685  		{Name: "WORKER_SIDECAR_IMAGE", Value: image},
   686  		{Name: "WORKER_IMAGE_PULL_POLICY", Value: "IfNotPresent"},
   687  		{Name: WorkerServiceAccountEnvVar, Value: opts.WorkerServiceAccountName},
   688  		{Name: "PACHD_VERSION", Value: opts.Version},
   689  		{Name: "METRICS", Value: strconv.FormatBool(opts.Metrics)},
   690  		{Name: "LOG_LEVEL", Value: opts.LogLevel},
   691  		{Name: "BLOCK_CACHE_BYTES", Value: opts.BlockCacheSize},
   692  		{Name: "IAM_ROLE", Value: opts.IAMRole},
   693  		{Name: "NO_EXPOSE_DOCKER_SOCKET", Value: strconv.FormatBool(opts.NoExposeDockerSocket)},
   694  		{Name: auth.DisableAuthenticationEnvVar, Value: strconv.FormatBool(opts.DisableAuthentication)},
   695  		{
   696  			Name: "PACH_NAMESPACE",
   697  			ValueFrom: &v1.EnvVarSource{
   698  				FieldRef: &v1.ObjectFieldSelector{
   699  					APIVersion: "v1",
   700  					FieldPath:  "metadata.namespace",
   701  				},
   702  			},
   703  		},
   704  		{
   705  			Name: "PACHD_MEMORY_REQUEST",
   706  			ValueFrom: &v1.EnvVarSource{
   707  				ResourceFieldRef: &v1.ResourceFieldSelector{
   708  					ContainerName: "pachd",
   709  					Resource:      "requests.memory",
   710  				},
   711  			},
   712  		},
   713  		{Name: "EXPOSE_OBJECT_API", Value: strconv.FormatBool(opts.ExposeObjectAPI)},
   714  		{Name: "CLUSTER_DEPLOYMENT_ID", Value: opts.ClusterDeploymentID},
   715  		{Name: RequireCriticalServersOnlyEnvVar, Value: strconv.FormatBool(opts.RequireCriticalServersOnly)},
   716  		{
   717  			Name: "PACHD_POD_NAME",
   718  			ValueFrom: &v1.EnvVarSource{
   719  				FieldRef: &v1.ObjectFieldSelector{
   720  					APIVersion: "v1",
   721  					FieldPath:  "metadata.name",
   722  				},
   723  			},
   724  		},
   725  		// TODO: Setting the default explicitly to ensure the environment variable is set since we pull directly
   726  		// from it for setting up the worker client. Probably should not be pulling directly from environment variables.
   727  		{
   728  			Name:  client.PPSWorkerPortEnv,
   729  			Value: "80",
   730  		},
   731  	}
   732  	envVars = append(envVars, GetSecretEnvVars("")...)
   733  	envVars = append(envVars, getStorageEnvVars(opts)...)
   734  	return &apps.Deployment{
   735  		TypeMeta: metav1.TypeMeta{
   736  			Kind:       "Deployment",
   737  			APIVersion: "apps/v1",
   738  		},
   739  		ObjectMeta: objectMeta(pachdName, labels(pachdName), nil, opts.Namespace),
   740  		Spec: apps.DeploymentSpec{
   741  			Replicas: replicas(1),
   742  			Selector: &metav1.LabelSelector{
   743  				MatchLabels: labels(pachdName),
   744  			},
   745  			Template: v1.PodTemplateSpec{
   746  				ObjectMeta: objectMeta(pachdName, labels(pachdName),
   747  					map[string]string{IAMAnnotation: opts.IAMRole}, opts.Namespace),
   748  				Spec: v1.PodSpec{
   749  					Containers: []v1.Container{
   750  						{
   751  							Name:    pachdName,
   752  							Image:   image,
   753  							Command: []string{"/pachd"},
   754  							Env:     envVars,
   755  							Ports: []v1.ContainerPort{
   756  								{
   757  									ContainerPort: opts.PachdPort, // also set in cmd/pachd/main.go
   758  									Protocol:      "TCP",
   759  									Name:          "api-grpc-port",
   760  								},
   761  								{
   762  									ContainerPort: opts.TracePort, // also set in cmd/pachd/main.go
   763  									Name:          "trace-port",
   764  								},
   765  								{
   766  									ContainerPort: opts.HTTPPort, // also set in cmd/pachd/main.go
   767  									Protocol:      "TCP",
   768  									Name:          "api-http-port",
   769  								},
   770  								{
   771  									ContainerPort: opts.PeerPort, // also set in cmd/pachd/main.go
   772  									Protocol:      "TCP",
   773  									Name:          "peer-port",
   774  								},
   775  								{
   776  									ContainerPort: githook.GitHookPort,
   777  									Protocol:      "TCP",
   778  									Name:          "api-git-port",
   779  								},
   780  								{
   781  									ContainerPort: auth.SamlPort,
   782  									Protocol:      "TCP",
   783  									Name:          "saml-port",
   784  								},
   785  								{
   786  									ContainerPort: auth.OidcPort,
   787  									Protocol:      "TCP",
   788  									Name:          "oidc-port",
   789  								},
   790  								{
   791  									ContainerPort: int32(PrometheusPort),
   792  									Protocol:      "TCP",
   793  									Name:          "prom-metrics",
   794  								},
   795  							},
   796  							VolumeMounts:    volumeMounts,
   797  							ImagePullPolicy: "IfNotPresent",
   798  							Resources:       resourceRequirements,
   799  							ReadinessProbe: &v1.Probe{
   800  								Handler: v1.Handler{
   801  									Exec: &v1.ExecAction{
   802  										Command: []string{"/pachd", "--readiness"},
   803  									},
   804  								},
   805  							},
   806  						},
   807  					},
   808  					ServiceAccountName: ServiceAccountName,
   809  					Volumes:            volumes,
   810  					ImagePullSecrets:   imagePullSecrets(opts),
   811  				},
   812  			},
   813  		},
   814  	}
   815  }
   816  
   817  // PachdService returns a pachd service.
   818  func PachdService(opts *AssetOpts) *v1.Service {
   819  	prometheusAnnotations := map[string]string{
   820  		"prometheus.io/scrape": "true",
   821  		"prometheus.io/port":   strconv.Itoa(PrometheusPort),
   822  	}
   823  	return &v1.Service{
   824  		TypeMeta: metav1.TypeMeta{
   825  			Kind:       "Service",
   826  			APIVersion: "v1",
   827  		},
   828  		ObjectMeta: objectMeta(pachdName, labels(pachdName), prometheusAnnotations, opts.Namespace),
   829  		Spec: v1.ServiceSpec{
   830  			Type: v1.ServiceTypeNodePort,
   831  			Selector: map[string]string{
   832  				"app": pachdName,
   833  			},
   834  			Ports: []v1.ServicePort{
   835  				// NOTE: do not put any new ports before `api-grpc-port`, as
   836  				// it'll change k8s SERVICE_PORT env var values
   837  				{
   838  					Port:     650, // also set in cmd/pachd/main.go
   839  					Name:     "api-grpc-port",
   840  					NodePort: 30650,
   841  				},
   842  				{
   843  					Port:     651, // also set in cmd/pachd/main.go
   844  					Name:     "trace-port",
   845  					NodePort: 30651,
   846  				},
   847  				{
   848  					Port:     652, // also set in cmd/pachd/main.go
   849  					Name:     "api-http-port",
   850  					NodePort: 30652,
   851  				},
   852  				{
   853  					Port:     auth.SamlPort,
   854  					Name:     "saml-port",
   855  					NodePort: 30000 + auth.SamlPort,
   856  				},
   857  				{
   858  					Port:     auth.OidcPort,
   859  					Name:     "oidc-port",
   860  					NodePort: 30000 + auth.OidcPort,
   861  				},
   862  				{
   863  					Port:     githook.GitHookPort,
   864  					Name:     "api-git-port",
   865  					NodePort: githook.NodePort(),
   866  				},
   867  				{
   868  					Port:     600, // also set in cmd/pachd/main.go
   869  					Name:     "s3gateway-port",
   870  					NodePort: 30600,
   871  				},
   872  				{
   873  					Port:       656,
   874  					Name:       "prom-metrics",
   875  					NodePort:   30656,
   876  					Protocol:   v1.ProtocolTCP,
   877  					TargetPort: intstr.FromInt(656),
   878  				},
   879  			},
   880  		},
   881  	}
   882  }
   883  
   884  // PachdPeerService returns an internal pachd service. This service will
   885  // reference the PeerPorr, which does not employ TLS even if cluster TLS is
   886  // enabled. Because of this, the service is a `ClusterIP` type (i.e. not
   887  // exposed outside of the cluster.)
   888  func PachdPeerService(opts *AssetOpts) *v1.Service {
   889  	return &v1.Service{
   890  		TypeMeta: metav1.TypeMeta{
   891  			Kind:       "Service",
   892  			APIVersion: "v1",
   893  		},
   894  		ObjectMeta: objectMeta(fmt.Sprintf("%s-peer", pachdName), labels(pachdName), map[string]string{}, opts.Namespace),
   895  		Spec: v1.ServiceSpec{
   896  			Type: v1.ServiceTypeClusterIP,
   897  			Selector: map[string]string{
   898  				"app": pachdName,
   899  			},
   900  			Ports: []v1.ServicePort{
   901  				{
   902  					Port:       30653,
   903  					Name:       "api-grpc-peer-port",
   904  					TargetPort: intstr.FromInt(653), // also set in cmd/pachd/main.go
   905  				},
   906  			},
   907  		},
   908  	}
   909  }
   910  
   911  // GithookService returns a k8s service that exposes a public IP
   912  func GithookService(namespace string) *v1.Service {
   913  	name := "githook"
   914  	return &v1.Service{
   915  		TypeMeta: metav1.TypeMeta{
   916  			Kind:       "Service",
   917  			APIVersion: "v1",
   918  		},
   919  		ObjectMeta: objectMeta(name, labels(name), nil, namespace),
   920  		Spec: v1.ServiceSpec{
   921  			Type: v1.ServiceTypeLoadBalancer,
   922  			Selector: map[string]string{
   923  				"app": pachdName,
   924  			},
   925  			Ports: []v1.ServicePort{
   926  				{
   927  					TargetPort: intstr.FromInt(githook.GitHookPort),
   928  					Name:       "api-git-port",
   929  					Port:       githook.ExternalPort(),
   930  				},
   931  			},
   932  		},
   933  	}
   934  }
   935  
   936  // EtcdDeployment returns an etcd k8s Deployment.
   937  func EtcdDeployment(opts *AssetOpts, hostPath string) *apps.Deployment {
   938  	cpu := resource.MustParse(opts.EtcdCPURequest)
   939  	mem := resource.MustParse(opts.EtcdMemRequest)
   940  	var volumes []v1.Volume
   941  	if hostPath == "" {
   942  		volumes = []v1.Volume{
   943  			{
   944  				Name: "etcd-storage",
   945  				VolumeSource: v1.VolumeSource{
   946  					PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
   947  						ClaimName: etcdVolumeClaimName,
   948  					},
   949  				},
   950  			},
   951  		}
   952  	} else {
   953  		pathType := v1.HostPathDirectoryOrCreate
   954  		volumes = []v1.Volume{
   955  			{
   956  				Name: "etcd-storage",
   957  				VolumeSource: v1.VolumeSource{
   958  					HostPath: &v1.HostPathVolumeSource{
   959  						Path: path.Join(hostPath, "etcd"),
   960  						Type: &pathType,
   961  					},
   962  				},
   963  			},
   964  		}
   965  	}
   966  	resourceRequirements := v1.ResourceRequirements{
   967  		Requests: v1.ResourceList{
   968  			v1.ResourceCPU:    cpu,
   969  			v1.ResourceMemory: mem,
   970  		},
   971  	}
   972  	if !opts.NoGuaranteed {
   973  		resourceRequirements.Limits = v1.ResourceList{
   974  			v1.ResourceCPU:    cpu,
   975  			v1.ResourceMemory: mem,
   976  		}
   977  	}
   978  	// Don't want to strip the registry out of etcdImage since it's from quay
   979  	// not docker hub.
   980  	image := etcdImage
   981  	if opts.Registry != "" {
   982  		image = AddRegistry(opts.Registry, etcdImage)
   983  	}
   984  	return &apps.Deployment{
   985  		TypeMeta: metav1.TypeMeta{
   986  			Kind:       "Deployment",
   987  			APIVersion: "apps/v1",
   988  		},
   989  		ObjectMeta: objectMeta(etcdName, labels(etcdName), nil, opts.Namespace),
   990  		Spec: apps.DeploymentSpec{
   991  			Replicas: replicas(1),
   992  			Selector: &metav1.LabelSelector{
   993  				MatchLabels: labels(etcdName),
   994  			},
   995  			Template: v1.PodTemplateSpec{
   996  				ObjectMeta: objectMeta(etcdName, labels(etcdName), nil, opts.Namespace),
   997  				Spec: v1.PodSpec{
   998  					Containers: []v1.Container{
   999  						{
  1000  							Name:  etcdName,
  1001  							Image: image,
  1002  							//TODO figure out how to get a cluster of these to talk to each other
  1003  							Command: etcdCmd,
  1004  							Ports: []v1.ContainerPort{
  1005  								{
  1006  									ContainerPort: 2379,
  1007  									Name:          "client-port",
  1008  								},
  1009  								{
  1010  									ContainerPort: 2380,
  1011  									Name:          "peer-port",
  1012  								},
  1013  							},
  1014  							VolumeMounts: []v1.VolumeMount{
  1015  								{
  1016  									Name:      "etcd-storage",
  1017  									MountPath: "/var/data/etcd",
  1018  								},
  1019  							},
  1020  							ImagePullPolicy: "IfNotPresent",
  1021  							Resources:       resourceRequirements,
  1022  						},
  1023  					},
  1024  					Volumes:          volumes,
  1025  					ImagePullSecrets: imagePullSecrets(opts),
  1026  				},
  1027  			},
  1028  		},
  1029  	}
  1030  }
  1031  
  1032  // EtcdStorageClass creates a storage class used for dynamic volume
  1033  // provisioning.  Currently dynamic volume provisioning only works
  1034  // on AWS and GCE.
  1035  func EtcdStorageClass(opts *AssetOpts, backend Backend) (interface{}, error) {
  1036  	return makeStorageClass(opts, backend, defaultEtcdStorageClassName, labels(etcdName))
  1037  }
  1038  
  1039  // PostgresStorageClass creates a storage class used for dynamic volume
  1040  // provisioning.  Currently dynamic volume provisioning only works
  1041  // on AWS and GCE.
  1042  func PostgresStorageClass(opts *AssetOpts, backend Backend) (interface{}, error) {
  1043  	return makeStorageClass(opts, backend, defaultPostgresStorageClassName, labels(postgresName))
  1044  }
  1045  
  1046  func makeStorageClass(
  1047  	opts *AssetOpts,
  1048  	backend Backend,
  1049  	storageClassName string,
  1050  	storageClassLabels map[string]string,
  1051  ) (interface{}, error) {
  1052  	sc := map[string]interface{}{
  1053  		"apiVersion": "storage.k8s.io/v1",
  1054  		"kind":       "StorageClass",
  1055  		"metadata": map[string]interface{}{
  1056  			"name":      storageClassName,
  1057  			"labels":    storageClassLabels,
  1058  			"namespace": opts.Namespace,
  1059  		},
  1060  		"allowVolumeExpansion": true,
  1061  	}
  1062  	switch backend {
  1063  	case GoogleBackend:
  1064  		sc["provisioner"] = "kubernetes.io/gce-pd"
  1065  		sc["parameters"] = map[string]string{
  1066  			"type": "pd-ssd",
  1067  		}
  1068  	case AmazonBackend:
  1069  		sc["provisioner"] = "kubernetes.io/aws-ebs"
  1070  		sc["parameters"] = map[string]string{
  1071  			"type": "gp2",
  1072  		}
  1073  	default:
  1074  		return nil, nil
  1075  	}
  1076  	return sc, nil
  1077  }
  1078  
  1079  // EtcdVolume creates a persistent volume backed by a volume with name "name"
  1080  func EtcdVolume(persistentDiskBackend Backend, opts *AssetOpts,
  1081  	hostPath string, name string, size int) (*v1.PersistentVolume, error) {
  1082  	return makePersistentVolume(persistentDiskBackend, opts, hostPath, name, size, etcdVolumeName, labels(etcdName))
  1083  }
  1084  
  1085  // PostgresVolume creates a persistent volume backed by a volume with name "name"
  1086  func PostgresVolume(persistentDiskBackend Backend, opts *AssetOpts,
  1087  	hostPath string, name string, size int) (*v1.PersistentVolume, error) {
  1088  	return makePersistentVolume(persistentDiskBackend, opts, hostPath, name, size, postgresVolumeName, labels(postgresName))
  1089  }
  1090  
  1091  func makePersistentVolume(
  1092  	persistentDiskBackend Backend,
  1093  	opts *AssetOpts,
  1094  	hostPath string,
  1095  	name string,
  1096  	size int,
  1097  	volumeName string,
  1098  	volumeLabels map[string]string,
  1099  ) (*v1.PersistentVolume, error) {
  1100  	spec := &v1.PersistentVolume{
  1101  		TypeMeta: metav1.TypeMeta{
  1102  			Kind:       "PersistentVolume",
  1103  			APIVersion: "v1",
  1104  		},
  1105  		ObjectMeta: objectMeta(volumeName, volumeLabels, nil, opts.Namespace),
  1106  		Spec: v1.PersistentVolumeSpec{
  1107  			Capacity: map[v1.ResourceName]resource.Quantity{
  1108  				"storage": resource.MustParse(fmt.Sprintf("%vGi", size)),
  1109  			},
  1110  			AccessModes:                   []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
  1111  			PersistentVolumeReclaimPolicy: v1.PersistentVolumeReclaimRetain,
  1112  		},
  1113  	}
  1114  
  1115  	switch persistentDiskBackend {
  1116  	case AmazonBackend:
  1117  		spec.Spec.PersistentVolumeSource = v1.PersistentVolumeSource{
  1118  			AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
  1119  				FSType:   "ext4",
  1120  				VolumeID: name,
  1121  			},
  1122  		}
  1123  	case GoogleBackend:
  1124  		spec.Spec.PersistentVolumeSource = v1.PersistentVolumeSource{
  1125  			GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{
  1126  				FSType: "ext4",
  1127  				PDName: name,
  1128  			},
  1129  		}
  1130  	case MicrosoftBackend:
  1131  		dataDiskURI := name
  1132  		split := strings.Split(name, "/")
  1133  		diskName := split[len(split)-1]
  1134  
  1135  		spec.Spec.PersistentVolumeSource = v1.PersistentVolumeSource{
  1136  			AzureDisk: &v1.AzureDiskVolumeSource{
  1137  				DiskName:    diskName,
  1138  				DataDiskURI: dataDiskURI,
  1139  			},
  1140  		}
  1141  	case MinioBackend:
  1142  		fallthrough
  1143  	case LocalBackend:
  1144  		pathType := v1.HostPathDirectoryOrCreate
  1145  		spec.Spec.PersistentVolumeSource = v1.PersistentVolumeSource{
  1146  			HostPath: &v1.HostPathVolumeSource{
  1147  				Path: path.Join(hostPath, volumeName),
  1148  				Type: &pathType,
  1149  			},
  1150  		}
  1151  	default:
  1152  		return nil, errors.Errorf("cannot generate volume spec for unknown backend \"%v\"", persistentDiskBackend)
  1153  	}
  1154  	return spec, nil
  1155  }
  1156  
  1157  // EtcdVolumeClaim creates a persistent volume claim of 'size' GB.
  1158  //
  1159  // Note that if you're controlling Etcd with a Stateful Set, this is
  1160  // unnecessary (the stateful set controller will create PVCs automatically).
  1161  func EtcdVolumeClaim(size int, opts *AssetOpts) *v1.PersistentVolumeClaim {
  1162  	return makeVolumeClaim(size, opts, etcdVolumeName, etcdVolumeClaimName, labels(etcdName))
  1163  }
  1164  
  1165  // PostgresVolumeClaim creates a persistent volume claim of 'size' GB.
  1166  //
  1167  // Note that if you're controlling Postgres with a Stateful Set, this is
  1168  // unnecessary (the stateful set controller will create PVCs automatically).
  1169  func PostgresVolumeClaim(size int, opts *AssetOpts) *v1.PersistentVolumeClaim {
  1170  	return makeVolumeClaim(size, opts, postgresVolumeName, postgresVolumeClaimName, labels(postgresName))
  1171  }
  1172  
  1173  func makeVolumeClaim(
  1174  	size int,
  1175  	opts *AssetOpts,
  1176  	volumeName string,
  1177  	volumeClaimName string,
  1178  	volumeClaimLabels map[string]string,
  1179  ) *v1.PersistentVolumeClaim {
  1180  	return &v1.PersistentVolumeClaim{
  1181  		TypeMeta: metav1.TypeMeta{
  1182  			Kind:       "PersistentVolumeClaim",
  1183  			APIVersion: "v1",
  1184  		},
  1185  		ObjectMeta: objectMeta(volumeClaimName, volumeClaimLabels, nil, opts.Namespace),
  1186  		Spec: v1.PersistentVolumeClaimSpec{
  1187  			Resources: v1.ResourceRequirements{
  1188  				Requests: map[v1.ResourceName]resource.Quantity{
  1189  					"storage": resource.MustParse(fmt.Sprintf("%vGi", size)),
  1190  				},
  1191  			},
  1192  			AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce},
  1193  			VolumeName:  volumeName,
  1194  		},
  1195  	}
  1196  }
  1197  
  1198  // EtcdNodePortService returns a NodePort etcd service. This will let non-etcd
  1199  // pods talk to etcd
  1200  func EtcdNodePortService(local bool, opts *AssetOpts) *v1.Service {
  1201  	var clientNodePort int32
  1202  	if local {
  1203  		clientNodePort = 32379
  1204  	}
  1205  	return &v1.Service{
  1206  		TypeMeta: metav1.TypeMeta{
  1207  			Kind:       "Service",
  1208  			APIVersion: "v1",
  1209  		},
  1210  		ObjectMeta: objectMeta(etcdName, labels(etcdName), nil, opts.Namespace),
  1211  		Spec: v1.ServiceSpec{
  1212  			Type: v1.ServiceTypeNodePort,
  1213  			Selector: map[string]string{
  1214  				"app": etcdName,
  1215  			},
  1216  			Ports: []v1.ServicePort{
  1217  				{
  1218  					Port:     2379,
  1219  					Name:     "client-port",
  1220  					NodePort: clientNodePort,
  1221  				},
  1222  			},
  1223  		},
  1224  	}
  1225  }
  1226  
  1227  // EtcdHeadlessService returns a headless etcd service, which is only for DNS
  1228  // resolution.
  1229  func EtcdHeadlessService(opts *AssetOpts) *v1.Service {
  1230  	return &v1.Service{
  1231  		TypeMeta: metav1.TypeMeta{
  1232  			Kind:       "Service",
  1233  			APIVersion: "v1",
  1234  		},
  1235  		ObjectMeta: objectMeta(etcdHeadlessServiceName, labels(etcdName), nil, opts.Namespace),
  1236  		Spec: v1.ServiceSpec{
  1237  			Selector: map[string]string{
  1238  				"app": etcdName,
  1239  			},
  1240  			ClusterIP: "None",
  1241  			Ports: []v1.ServicePort{
  1242  				{
  1243  					Name: "peer-port",
  1244  					Port: 2380,
  1245  				},
  1246  			},
  1247  		},
  1248  	}
  1249  }
  1250  
  1251  // EtcdStatefulSet returns a stateful set that manages an etcd cluster
  1252  func EtcdStatefulSet(opts *AssetOpts, backend Backend, diskSpace int) interface{} {
  1253  	mem := resource.MustParse(opts.EtcdMemRequest)
  1254  	cpu := resource.MustParse(opts.EtcdCPURequest)
  1255  	initialCluster := make([]string, 0, opts.EtcdNodes)
  1256  	for i := 0; i < opts.EtcdNodes; i++ {
  1257  		url := fmt.Sprintf("http://etcd-%d.etcd-headless.${NAMESPACE}.svc.cluster.local:2380", i)
  1258  		initialCluster = append(initialCluster, fmt.Sprintf("etcd-%d=%s", i, url))
  1259  	}
  1260  	// Because we need to refer to some environment variables set the by the
  1261  	// k8s downward API, we define the command for running etcd here, and then
  1262  	// actually run it below via '/bin/sh -c ${CMD}'
  1263  	etcdCmd := append(etcdCmd,
  1264  		"--listen-peer-urls=http://0.0.0.0:2380",
  1265  		"--initial-cluster-token=pach-cluster", // unique ID
  1266  		"--initial-advertise-peer-urls=http://${ETCD_NAME}.etcd-headless.${NAMESPACE}.svc.cluster.local:2380",
  1267  		"--initial-cluster="+strings.Join(initialCluster, ","),
  1268  	)
  1269  	for i, str := range etcdCmd {
  1270  		etcdCmd[i] = fmt.Sprintf("\"%s\"", str) // quote all arguments, for shell
  1271  	}
  1272  
  1273  	var pvcTemplates []interface{}
  1274  	switch backend {
  1275  	case GoogleBackend, AmazonBackend:
  1276  		storageClassName := opts.EtcdStorageClassName
  1277  		if storageClassName == "" {
  1278  			storageClassName = defaultEtcdStorageClassName
  1279  		}
  1280  		pvcTemplates = []interface{}{
  1281  			map[string]interface{}{
  1282  				"metadata": map[string]interface{}{
  1283  					"name":   etcdVolumeClaimName,
  1284  					"labels": labels(etcdName),
  1285  					"annotations": map[string]string{
  1286  						"volume.beta.kubernetes.io/storage-class": storageClassName,
  1287  					},
  1288  					"namespace": opts.Namespace,
  1289  				},
  1290  				"spec": map[string]interface{}{
  1291  					"resources": map[string]interface{}{
  1292  						"requests": map[string]interface{}{
  1293  							"storage": resource.MustParse(fmt.Sprintf("%vGi", diskSpace)),
  1294  						},
  1295  					},
  1296  					"accessModes": []string{"ReadWriteOnce"},
  1297  				},
  1298  			},
  1299  		}
  1300  	default:
  1301  		pvcTemplates = []interface{}{
  1302  			map[string]interface{}{
  1303  				"metadata": map[string]interface{}{
  1304  					"name":      etcdVolumeClaimName,
  1305  					"labels":    labels(etcdName),
  1306  					"namespace": opts.Namespace,
  1307  				},
  1308  				"spec": map[string]interface{}{
  1309  					"resources": map[string]interface{}{
  1310  						"requests": map[string]interface{}{
  1311  							"storage": resource.MustParse(fmt.Sprintf("%vGi", diskSpace)),
  1312  						},
  1313  					},
  1314  					"accessModes": []string{"ReadWriteOnce"},
  1315  				},
  1316  			},
  1317  		}
  1318  	}
  1319  	var imagePullSecrets []map[string]string
  1320  	if opts.ImagePullSecret != "" {
  1321  		imagePullSecrets = append(imagePullSecrets, map[string]string{"name": opts.ImagePullSecret})
  1322  	}
  1323  	// As of March 17, 2017, the Kubernetes client does not include structs for
  1324  	// Stateful Set, so we generate the kubernetes manifest using raw json.
  1325  	// TODO(msteffen): we're now upgrading our kubernetes client, so we should be
  1326  	// abe to rewrite this spec using k8s client structs
  1327  	image := etcdImage
  1328  	if opts.Registry != "" {
  1329  		image = AddRegistry(opts.Registry, etcdImage)
  1330  	}
  1331  	return map[string]interface{}{
  1332  		"apiVersion": "apps/v1",
  1333  		"kind":       "StatefulSet",
  1334  		"metadata": map[string]interface{}{
  1335  			"name":      etcdName,
  1336  			"labels":    labels(etcdName),
  1337  			"namespace": opts.Namespace,
  1338  		},
  1339  		"spec": map[string]interface{}{
  1340  			// Effectively configures a RC
  1341  			"serviceName": etcdHeadlessServiceName,
  1342  			"replicas":    int(opts.EtcdNodes),
  1343  			"selector": map[string]interface{}{
  1344  				"matchLabels": labels(etcdName),
  1345  			},
  1346  
  1347  			// pod template
  1348  			"template": map[string]interface{}{
  1349  				"metadata": map[string]interface{}{
  1350  					"name":      etcdName,
  1351  					"labels":    labels(etcdName),
  1352  					"namespace": opts.Namespace,
  1353  				},
  1354  				"spec": map[string]interface{}{
  1355  					"imagePullSecrets": imagePullSecrets,
  1356  					"containers": []interface{}{
  1357  						map[string]interface{}{
  1358  							"name":    etcdName,
  1359  							"image":   image,
  1360  							"command": []string{"/bin/sh", "-c"},
  1361  							"args":    []string{strings.Join(etcdCmd, " ")},
  1362  							// Use the downward API to pass the pod name to etcd. This sets
  1363  							// the etcd-internal name of each node to its pod name.
  1364  							"env": []map[string]interface{}{{
  1365  								"name": "ETCD_NAME",
  1366  								"valueFrom": map[string]interface{}{
  1367  									"fieldRef": map[string]interface{}{
  1368  										"apiVersion": "v1",
  1369  										"fieldPath":  "metadata.name",
  1370  									},
  1371  								},
  1372  							}, {
  1373  								"name": "NAMESPACE",
  1374  								"valueFrom": map[string]interface{}{
  1375  									"fieldRef": map[string]interface{}{
  1376  										"apiVersion": "v1",
  1377  										"fieldPath":  "metadata.namespace",
  1378  									},
  1379  								},
  1380  							}},
  1381  							"ports": []interface{}{
  1382  								map[string]interface{}{
  1383  									"containerPort": 2379,
  1384  									"name":          "client-port",
  1385  								},
  1386  								map[string]interface{}{
  1387  									"containerPort": 2380,
  1388  									"name":          "peer-port",
  1389  								},
  1390  							},
  1391  							"volumeMounts": []interface{}{
  1392  								map[string]interface{}{
  1393  									"name":      etcdVolumeClaimName,
  1394  									"mountPath": "/var/data/etcd",
  1395  								},
  1396  							},
  1397  							"imagePullPolicy": "IfNotPresent",
  1398  							"resources": map[string]interface{}{
  1399  								"requests": map[string]interface{}{
  1400  									string(v1.ResourceCPU):    cpu.String(),
  1401  									string(v1.ResourceMemory): mem.String(),
  1402  								},
  1403  							},
  1404  						},
  1405  					},
  1406  				},
  1407  			},
  1408  			"volumeClaimTemplates": pvcTemplates,
  1409  		},
  1410  	}
  1411  }
  1412  
  1413  // DashDeployment creates a Deployment for the pachyderm dashboard.
  1414  func DashDeployment(opts *AssetOpts) *apps.Deployment {
  1415  	return &apps.Deployment{
  1416  		TypeMeta: metav1.TypeMeta{
  1417  			Kind:       "Deployment",
  1418  			APIVersion: "apps/v1",
  1419  		},
  1420  		ObjectMeta: objectMeta(dashName, labels(dashName), nil, opts.Namespace),
  1421  		Spec: apps.DeploymentSpec{
  1422  			Selector: &metav1.LabelSelector{
  1423  				MatchLabels: labels(dashName),
  1424  			},
  1425  			Template: v1.PodTemplateSpec{
  1426  				ObjectMeta: objectMeta(dashName, labels(dashName), nil, opts.Namespace),
  1427  				Spec: v1.PodSpec{
  1428  					Containers: []v1.Container{
  1429  						{
  1430  							Name:  dashName,
  1431  							Image: AddRegistry(opts.Registry, opts.DashImage),
  1432  							Ports: []v1.ContainerPort{
  1433  								{
  1434  									ContainerPort: 8080,
  1435  									Name:          "dash-http",
  1436  								},
  1437  							},
  1438  							ImagePullPolicy: "IfNotPresent",
  1439  						},
  1440  						{
  1441  							Name:  grpcProxyName,
  1442  							Image: AddRegistry(opts.Registry, grpcProxyImage),
  1443  							Ports: []v1.ContainerPort{
  1444  								{
  1445  									ContainerPort: 8081,
  1446  									Name:          "grpc-proxy-http",
  1447  								},
  1448  							},
  1449  							ImagePullPolicy: "IfNotPresent",
  1450  						},
  1451  					},
  1452  					ImagePullSecrets: imagePullSecrets(opts),
  1453  				},
  1454  			},
  1455  		},
  1456  	}
  1457  }
  1458  
  1459  // DashService creates a Service for the pachyderm dashboard.
  1460  func DashService(opts *AssetOpts) *v1.Service {
  1461  	return &v1.Service{
  1462  		TypeMeta: metav1.TypeMeta{
  1463  			Kind:       "Service",
  1464  			APIVersion: "v1",
  1465  		},
  1466  		ObjectMeta: objectMeta(dashName, labels(dashName), nil, opts.Namespace),
  1467  		Spec: v1.ServiceSpec{
  1468  			Type:     v1.ServiceTypeNodePort,
  1469  			Selector: labels(dashName),
  1470  			Ports: []v1.ServicePort{
  1471  				{
  1472  					Port:     8080,
  1473  					Name:     "dash-http",
  1474  					NodePort: 30080,
  1475  				},
  1476  				{
  1477  					Port:     8081,
  1478  					Name:     "grpc-proxy-http",
  1479  					NodePort: 30081,
  1480  				},
  1481  			},
  1482  		},
  1483  	}
  1484  }
  1485  
  1486  // PostgresDeployment generates a Deployment for the pachyderm postgres instance.
  1487  func PostgresDeployment(opts *AssetOpts, hostPath string) *apps.Deployment {
  1488  	cpu := resource.MustParse(opts.PostgresCPURequest)
  1489  	mem := resource.MustParse(opts.PostgresMemRequest)
  1490  	var volumes []v1.Volume
  1491  	if hostPath == "" {
  1492  		volumes = []v1.Volume{
  1493  			{
  1494  				Name: "postgres-storage",
  1495  				VolumeSource: v1.VolumeSource{
  1496  					PersistentVolumeClaim: &v1.PersistentVolumeClaimVolumeSource{
  1497  						ClaimName: postgresVolumeClaimName,
  1498  					},
  1499  				},
  1500  			},
  1501  		}
  1502  	} else {
  1503  		volumes = []v1.Volume{
  1504  			{
  1505  				Name: "postgres-storage",
  1506  				VolumeSource: v1.VolumeSource{
  1507  					HostPath: &v1.HostPathVolumeSource{
  1508  						Path: filepath.Join(hostPath, "postgres"),
  1509  					},
  1510  				},
  1511  			},
  1512  		}
  1513  	}
  1514  	resourceRequirements := v1.ResourceRequirements{
  1515  		Requests: v1.ResourceList{
  1516  			v1.ResourceCPU:    cpu,
  1517  			v1.ResourceMemory: mem,
  1518  		},
  1519  	}
  1520  	if !opts.NoGuaranteed {
  1521  		resourceRequirements.Limits = v1.ResourceList{
  1522  			v1.ResourceCPU:    cpu,
  1523  			v1.ResourceMemory: mem,
  1524  		}
  1525  	}
  1526  	image := postgresImage
  1527  	if opts.Registry != "" {
  1528  		image = AddRegistry(opts.Registry, postgresImage)
  1529  	}
  1530  	return &apps.Deployment{
  1531  		TypeMeta: metav1.TypeMeta{
  1532  			Kind:       "Deployment",
  1533  			APIVersion: "apps/v1beta1",
  1534  		},
  1535  		ObjectMeta: objectMeta(postgresName, labels(postgresName), nil, opts.Namespace),
  1536  		Spec: apps.DeploymentSpec{
  1537  			Replicas: replicas(1),
  1538  			Selector: &metav1.LabelSelector{
  1539  				MatchLabels: labels(postgresName),
  1540  			},
  1541  			Template: v1.PodTemplateSpec{
  1542  				ObjectMeta: objectMeta(postgresName, labels(postgresName), nil, opts.Namespace),
  1543  				Spec: v1.PodSpec{
  1544  					Containers: []v1.Container{
  1545  						{
  1546  							Name:  postgresName,
  1547  							Image: image,
  1548  							//TODO figure out how to get a cluster of these to talk to each other
  1549  							Ports: []v1.ContainerPort{
  1550  								{
  1551  									ContainerPort: 5432,
  1552  									Name:          "client-port",
  1553  								},
  1554  							},
  1555  							VolumeMounts: []v1.VolumeMount{
  1556  								{
  1557  									Name:      "postgres-storage",
  1558  									MountPath: "/var/lib/postgresql/data",
  1559  								},
  1560  							},
  1561  							ImagePullPolicy: "IfNotPresent",
  1562  							Resources:       resourceRequirements,
  1563  							Env: []v1.EnvVar{
  1564  								{Name: "POSTGRES_DB", Value: "pgc"},
  1565  								{Name: "POSTGRES_USER", Value: "pachyderm"},
  1566  								{Name: "POSTGRES_PASSWORD", Value: "elephantastic"},
  1567  							},
  1568  						},
  1569  					},
  1570  					Volumes:          volumes,
  1571  					ImagePullSecrets: imagePullSecrets(opts),
  1572  				},
  1573  			},
  1574  		},
  1575  	}
  1576  }
  1577  
  1578  // PostgresService generates a Service for the pachyderm postgres instance.
  1579  func PostgresService(local bool, opts *AssetOpts) *v1.Service {
  1580  	var clientNodePort int32
  1581  	if local {
  1582  		clientNodePort = 32228
  1583  	}
  1584  	return &v1.Service{
  1585  		TypeMeta: metav1.TypeMeta{
  1586  			Kind:       "Service",
  1587  			APIVersion: "v1",
  1588  		},
  1589  		ObjectMeta: objectMeta(postgresName, labels(postgresName), nil, opts.Namespace),
  1590  		Spec: v1.ServiceSpec{
  1591  			Type: v1.ServiceTypeNodePort,
  1592  			Selector: map[string]string{
  1593  				"app": postgresName,
  1594  			},
  1595  			Ports: []v1.ServicePort{
  1596  				{
  1597  					Port:     5432,
  1598  					Name:     "client-port",
  1599  					NodePort: clientNodePort,
  1600  				},
  1601  			},
  1602  		},
  1603  	}
  1604  }
  1605  
  1606  // MinioSecret creates an amazon secret with the following parameters:
  1607  //   bucket - S3 bucket name
  1608  //   id     - S3 access key id
  1609  //   secret - S3 secret access key
  1610  //   endpoint  - S3 compatible endpoint
  1611  //   secure - set to true for a secure connection.
  1612  //   isS3V2 - Set to true if client follows S3V2
  1613  func MinioSecret(bucket string, id string, secret string, endpoint string, secure, isS3V2 bool) map[string][]byte {
  1614  	secureV := "0"
  1615  	if secure {
  1616  		secureV = "1"
  1617  	}
  1618  	s3V2 := "0"
  1619  	if isS3V2 {
  1620  		s3V2 = "1"
  1621  	}
  1622  	return map[string][]byte{
  1623  		"minio-bucket":    []byte(bucket),
  1624  		"minio-id":        []byte(id),
  1625  		"minio-secret":    []byte(secret),
  1626  		"minio-endpoint":  []byte(endpoint),
  1627  		"minio-secure":    []byte(secureV),
  1628  		"minio-signature": []byte(s3V2),
  1629  	}
  1630  }
  1631  
  1632  // WriteSecret writes a JSON-encoded k8s secret to the given writer.
  1633  // The secret uses the given map as data.
  1634  func WriteSecret(encoder serde.Encoder, data map[string][]byte, opts *AssetOpts) error {
  1635  	if opts.DashOnly {
  1636  		return nil
  1637  	}
  1638  	secret := &v1.Secret{
  1639  		TypeMeta: metav1.TypeMeta{
  1640  			Kind:       "Secret",
  1641  			APIVersion: "v1",
  1642  		},
  1643  		ObjectMeta: objectMeta(client.StorageSecretName, labels(client.StorageSecretName), nil, opts.Namespace),
  1644  		Data:       data,
  1645  	}
  1646  	return encoder.Encode(secret)
  1647  }
  1648  
  1649  // LocalSecret creates an empty secret.
  1650  func LocalSecret() map[string][]byte {
  1651  	return nil
  1652  }
  1653  
  1654  // AmazonSecret creates an amazon secret with the following parameters:
  1655  //   region         - AWS region
  1656  //   bucket         - S3 bucket name
  1657  //   id             - AWS access key id
  1658  //   secret         - AWS secret access key
  1659  //   token          - AWS access token
  1660  //   distribution   - cloudfront distribution
  1661  //   endpoint       - Custom endpoint (generally used for S3 compatible object stores)
  1662  //   advancedConfig - advanced configuration
  1663  func AmazonSecret(region, bucket, id, secret, token, distribution, endpoint string, advancedConfig *obj.AmazonAdvancedConfiguration) map[string][]byte {
  1664  	s := amazonBasicSecret(region, bucket, distribution, advancedConfig)
  1665  	s["amazon-id"] = []byte(id)
  1666  	s["amazon-secret"] = []byte(secret)
  1667  	s["amazon-token"] = []byte(token)
  1668  	s["custom-endpoint"] = []byte(endpoint)
  1669  	return s
  1670  }
  1671  
  1672  // AmazonVaultSecret creates an amazon secret with the following parameters:
  1673  //   region         - AWS region
  1674  //   bucket         - S3 bucket name
  1675  //   vaultAddress   - address/hostport of vault
  1676  //   vaultRole      - pachd's role in vault
  1677  //   vaultToken     - pachd's vault token
  1678  //   distribution   - cloudfront distribution
  1679  //   advancedConfig - advanced configuration
  1680  func AmazonVaultSecret(region, bucket, vaultAddress, vaultRole, vaultToken, distribution string, advancedConfig *obj.AmazonAdvancedConfiguration) map[string][]byte {
  1681  	s := amazonBasicSecret(region, bucket, distribution, advancedConfig)
  1682  	s["amazon-vault-addr"] = []byte(vaultAddress)
  1683  	s["amazon-vault-role"] = []byte(vaultRole)
  1684  	s["amazon-vault-token"] = []byte(vaultToken)
  1685  	return s
  1686  }
  1687  
  1688  // AmazonIAMRoleSecret creates an amazon secret with the following parameters:
  1689  //   region         - AWS region
  1690  //   bucket         - S3 bucket name
  1691  //   distribution   - cloudfront distribution
  1692  //   advancedConfig - advanced configuration
  1693  func AmazonIAMRoleSecret(region, bucket, distribution string, advancedConfig *obj.AmazonAdvancedConfiguration) map[string][]byte {
  1694  	return amazonBasicSecret(region, bucket, distribution, advancedConfig)
  1695  }
  1696  
  1697  func amazonBasicSecret(region, bucket, distribution string, advancedConfig *obj.AmazonAdvancedConfiguration) map[string][]byte {
  1698  	return map[string][]byte{
  1699  		"amazon-region":       []byte(region),
  1700  		"amazon-bucket":       []byte(bucket),
  1701  		"amazon-distribution": []byte(distribution),
  1702  		"retries":             []byte(strconv.Itoa(advancedConfig.Retries)),
  1703  		"timeout":             []byte(advancedConfig.Timeout),
  1704  		"upload-acl":          []byte(advancedConfig.UploadACL),
  1705  		"reverse":             []byte(strconv.FormatBool(advancedConfig.Reverse)),
  1706  		"part-size":           []byte(strconv.FormatInt(advancedConfig.PartSize, 10)),
  1707  		"max-upload-parts":    []byte(strconv.Itoa(advancedConfig.MaxUploadParts)),
  1708  		"disable-ssl":         []byte(strconv.FormatBool(advancedConfig.DisableSSL)),
  1709  		"no-verify-ssl":       []byte(strconv.FormatBool(advancedConfig.NoVerifySSL)),
  1710  		"log-options":         []byte(advancedConfig.LogOptions),
  1711  	}
  1712  }
  1713  
  1714  // GoogleSecret creates a google secret with a bucket name.
  1715  func GoogleSecret(bucket string, cred string) map[string][]byte {
  1716  	return map[string][]byte{
  1717  		"google-bucket": []byte(bucket),
  1718  		"google-cred":   []byte(cred),
  1719  	}
  1720  }
  1721  
  1722  // MicrosoftSecret creates a microsoft secret with following parameters:
  1723  //   container - Azure blob container
  1724  //   id    	   - Azure storage account name
  1725  //   secret    - Azure storage account key
  1726  func MicrosoftSecret(container string, id string, secret string) map[string][]byte {
  1727  	return map[string][]byte{
  1728  		"microsoft-container": []byte(container),
  1729  		"microsoft-id":        []byte(id),
  1730  		"microsoft-secret":    []byte(secret),
  1731  	}
  1732  }
  1733  
  1734  // WriteDashboardAssets writes the k8s config for deploying the Pachyderm
  1735  // dashboard to 'encoder'
  1736  func WriteDashboardAssets(encoder serde.Encoder, opts *AssetOpts) error {
  1737  	if err := encoder.Encode(DashService(opts)); err != nil {
  1738  		return err
  1739  	}
  1740  	return encoder.Encode(DashDeployment(opts))
  1741  }
  1742  
  1743  // WriteAssets writes the assets to encoder.
  1744  func WriteAssets(encoder serde.Encoder, opts *AssetOpts, objectStoreBackend Backend,
  1745  	persistentDiskBackend Backend, volumeSize int,
  1746  	hostPath string) error {
  1747  	fillDefaultResourceRequests(opts, persistentDiskBackend)
  1748  	if opts.DashOnly {
  1749  		if dashErr := WriteDashboardAssets(encoder, opts); dashErr != nil {
  1750  			return dashErr
  1751  		}
  1752  		return nil
  1753  	}
  1754  
  1755  	for _, sa := range ServiceAccounts(opts) {
  1756  		if err := encoder.Encode(sa); err != nil {
  1757  			return err
  1758  		}
  1759  	}
  1760  	if !opts.NoRBAC {
  1761  		if opts.LocalRoles {
  1762  			if err := encoder.Encode(Role(opts)); err != nil {
  1763  				return err
  1764  			}
  1765  			if err := encoder.Encode(RoleBinding(opts)); err != nil {
  1766  				return err
  1767  			}
  1768  		} else {
  1769  			if err := encoder.Encode(ClusterRole(opts)); err != nil {
  1770  				return err
  1771  			}
  1772  			if err := encoder.Encode(ClusterRoleBinding(opts)); err != nil {
  1773  				return err
  1774  			}
  1775  		}
  1776  		if err := encoder.Encode(workerRole(opts)); err != nil {
  1777  			return err
  1778  		}
  1779  		if err := encoder.Encode(workerRoleBinding(opts)); err != nil {
  1780  			return err
  1781  		}
  1782  	}
  1783  
  1784  	if opts.EtcdNodes > 0 && opts.EtcdVolume != "" {
  1785  		return errors.Errorf("only one of --dynamic-etcd-nodes and --static-etcd-volume should be given, but not both")
  1786  	}
  1787  
  1788  	// In the dynamic route, we create a storage class which dynamically
  1789  	// provisions volumes, and run etcd as a stateful set.
  1790  	// In the static route, we create a single volume, a single volume
  1791  	// claim, and run etcd as a replication controller with a single node.
  1792  	if persistentDiskBackend == LocalBackend {
  1793  		if err := encoder.Encode(EtcdDeployment(opts, hostPath)); err != nil {
  1794  			return err
  1795  		}
  1796  	} else if opts.EtcdNodes > 0 {
  1797  		// Create a StorageClass, if the user didn't provide one.
  1798  		if opts.EtcdStorageClassName == "" {
  1799  			sc, err := EtcdStorageClass(opts, persistentDiskBackend)
  1800  			if err != nil {
  1801  				return err
  1802  			}
  1803  			if sc != nil {
  1804  				if err = encoder.Encode(sc); err != nil {
  1805  					return err
  1806  				}
  1807  			}
  1808  		}
  1809  		if err := encoder.Encode(EtcdHeadlessService(opts)); err != nil {
  1810  			return err
  1811  		}
  1812  		if err := encoder.Encode(EtcdStatefulSet(opts, persistentDiskBackend, volumeSize)); err != nil {
  1813  			return err
  1814  		}
  1815  	} else if opts.EtcdVolume != "" {
  1816  		volume, err := EtcdVolume(persistentDiskBackend, opts, hostPath, opts.EtcdVolume, volumeSize)
  1817  		if err != nil {
  1818  			return err
  1819  		}
  1820  		if err = encoder.Encode(volume); err != nil {
  1821  			return err
  1822  		}
  1823  		if err = encoder.Encode(EtcdVolumeClaim(volumeSize, opts)); err != nil {
  1824  			return err
  1825  		}
  1826  		if err = encoder.Encode(EtcdDeployment(opts, "")); err != nil {
  1827  			return err
  1828  		}
  1829  	} else {
  1830  		return errors.Errorf("unless deploying locally, either --dynamic-etcd-nodes or --static-etcd-volume needs to be provided")
  1831  	}
  1832  	if err := encoder.Encode(EtcdNodePortService(persistentDiskBackend == LocalBackend, opts)); err != nil {
  1833  		return err
  1834  	}
  1835  
  1836  	if opts.StorageV2 {
  1837  		// In the dynamic route, we create a storage class which dynamically
  1838  		// provisions volumes, and run postgres as a stateful set.
  1839  		// In the static route, we create a single volume, a single volume
  1840  		// claim, and run etcd as a replication controller with a single node.
  1841  		if persistentDiskBackend == LocalBackend {
  1842  			if err := encoder.Encode(PostgresDeployment(opts, hostPath)); err != nil {
  1843  				return err
  1844  			}
  1845  		} else if opts.PostgresNodes > 0 {
  1846  			// Create a StorageClass, if the user didn't provide one.
  1847  			if opts.PostgresStorageClassName == "" {
  1848  				sc, err := PostgresStorageClass(opts, persistentDiskBackend)
  1849  				if err != nil {
  1850  					return err
  1851  				}
  1852  				if sc != nil {
  1853  					if err = encoder.Encode(sc); err != nil {
  1854  						return err
  1855  					}
  1856  				}
  1857  			}
  1858  			// TODO: is this necessary?
  1859  			// if err := encoder.Encode(PostgresHeadlessService(opts)); err != nil {
  1860  			// 	return err
  1861  			// }
  1862  			// TODO: add stateful set
  1863  			// if err := encoder.Encode(PostgresStatefulSet(opts, persistentDiskBackend, volumeSize)); err != nil {
  1864  			// 	return err
  1865  			// }
  1866  		} else if opts.PostgresVolume != "" {
  1867  			volume, err := PostgresVolume(persistentDiskBackend, opts, hostPath, opts.PostgresVolume, volumeSize)
  1868  			if err != nil {
  1869  				return err
  1870  			}
  1871  			if err = encoder.Encode(volume); err != nil {
  1872  				return err
  1873  			}
  1874  			if err = encoder.Encode(PostgresVolumeClaim(volumeSize, opts)); err != nil {
  1875  				return err
  1876  			}
  1877  			if err = encoder.Encode(PostgresDeployment(opts, "")); err != nil {
  1878  				return err
  1879  			}
  1880  		} else {
  1881  			return fmt.Errorf("unless deploying locally, either --dynamic-etcd-nodes or --static-etcd-volume needs to be provided")
  1882  		}
  1883  		if err := encoder.Encode(PostgresService(persistentDiskBackend == LocalBackend, opts)); err != nil {
  1884  			return err
  1885  		}
  1886  	}
  1887  
  1888  	if err := encoder.Encode(PachdService(opts)); err != nil {
  1889  		return err
  1890  	}
  1891  	if err := encoder.Encode(PachdPeerService(opts)); err != nil {
  1892  		return err
  1893  	}
  1894  	if err := encoder.Encode(PachdDeployment(opts, objectStoreBackend, hostPath)); err != nil {
  1895  		return err
  1896  	}
  1897  	if !opts.NoDash {
  1898  		if err := WriteDashboardAssets(encoder, opts); err != nil {
  1899  			return err
  1900  		}
  1901  	}
  1902  	if opts.TLS != nil {
  1903  		if err := WriteTLSSecret(encoder, opts); err != nil {
  1904  			return err
  1905  		}
  1906  	}
  1907  	return nil
  1908  }
  1909  
  1910  // WriteTLSSecret creates a new TLS secret in the kubernetes manifest
  1911  // (equivalent to one generate by 'kubectl create secret tls'). This will be
  1912  // mounted by the pachd pod and used as its TLS public certificate and private
  1913  // key
  1914  func WriteTLSSecret(encoder serde.Encoder, opts *AssetOpts) error {
  1915  	// Validate arguments
  1916  	if opts.DashOnly {
  1917  		return nil
  1918  	}
  1919  	if opts.TLS == nil {
  1920  		return errors.Errorf("internal error: WriteTLSSecret called but opts.TLS is nil")
  1921  	}
  1922  	if opts.TLS.ServerKey == "" {
  1923  		return errors.Errorf("internal error: WriteTLSSecret called but opts.TLS.ServerKey is \"\"")
  1924  	}
  1925  	if opts.TLS.ServerCert == "" {
  1926  		return errors.Errorf("internal error: WriteTLSSecret called but opts.TLS.ServerCert is \"\"")
  1927  	}
  1928  
  1929  	// Attempt to copy server cert and key files into config (kubernetes client
  1930  	// does the base64-encoding)
  1931  	certBytes, err := ioutil.ReadFile(opts.TLS.ServerCert)
  1932  	if err != nil {
  1933  		return errors.Wrapf(err, "could not open server cert at \"%s\"", opts.TLS.ServerCert)
  1934  	}
  1935  	keyBytes, err := ioutil.ReadFile(opts.TLS.ServerKey)
  1936  	if err != nil {
  1937  		return errors.Wrapf(err, "could not open server key at \"%s\"", opts.TLS.ServerKey)
  1938  	}
  1939  	secret := &v1.Secret{
  1940  		TypeMeta: metav1.TypeMeta{
  1941  			Kind:       "Secret",
  1942  			APIVersion: "v1",
  1943  		},
  1944  		ObjectMeta: objectMeta(tlsSecretName, labels(tlsSecretName), nil, opts.Namespace),
  1945  		Data: map[string][]byte{
  1946  			tls.CertFile: certBytes,
  1947  			tls.KeyFile:  keyBytes,
  1948  		},
  1949  	}
  1950  	return encoder.Encode(secret)
  1951  }
  1952  
  1953  // WriteLocalAssets writes assets to a local backend.
  1954  func WriteLocalAssets(encoder serde.Encoder, opts *AssetOpts, hostPath string) error {
  1955  	if err := WriteAssets(encoder, opts, LocalBackend, LocalBackend, 1 /* = volume size (gb) */, hostPath); err != nil {
  1956  		return err
  1957  	}
  1958  	if secretErr := WriteSecret(encoder, LocalSecret(), opts); secretErr != nil {
  1959  		return secretErr
  1960  	}
  1961  	return nil
  1962  }
  1963  
  1964  // WriteCustomAssets writes assets to a custom combination of object-store and persistent disk.
  1965  func WriteCustomAssets(encoder serde.Encoder, opts *AssetOpts, args []string, objectStoreBackend string,
  1966  	persistentDiskBackend string, secure, isS3V2 bool, advancedConfig *obj.AmazonAdvancedConfiguration) error {
  1967  	switch objectStoreBackend {
  1968  	case "s3":
  1969  		if len(args) != S3CustomArgs {
  1970  			return errors.Errorf("expected %d arguments for disk+s3 backend", S3CustomArgs)
  1971  		}
  1972  		volumeSize, err := strconv.Atoi(args[1])
  1973  		if err != nil {
  1974  			return errors.Errorf("volume size needs to be an integer; instead got %v", args[1])
  1975  		}
  1976  		objectStoreBackend := AmazonBackend
  1977  		// (bryce) use minio if we need v2 signing enabled.
  1978  		if isS3V2 {
  1979  			objectStoreBackend = MinioBackend
  1980  		}
  1981  		switch persistentDiskBackend {
  1982  		case "aws":
  1983  			if err := WriteAssets(encoder, opts, objectStoreBackend, AmazonBackend, volumeSize, ""); err != nil {
  1984  				return err
  1985  			}
  1986  		case "google":
  1987  			if err := WriteAssets(encoder, opts, objectStoreBackend, GoogleBackend, volumeSize, ""); err != nil {
  1988  				return err
  1989  			}
  1990  		case "azure":
  1991  			if err := WriteAssets(encoder, opts, objectStoreBackend, MicrosoftBackend, volumeSize, ""); err != nil {
  1992  				return err
  1993  			}
  1994  		default:
  1995  			return errors.Errorf("did not recognize the choice of persistent-disk")
  1996  		}
  1997  		bucket := args[2]
  1998  		id := args[3]
  1999  		secret := args[4]
  2000  		endpoint := args[5]
  2001  		if objectStoreBackend == MinioBackend {
  2002  			return WriteSecret(encoder, MinioSecret(bucket, id, secret, endpoint, secure, isS3V2), opts)
  2003  		}
  2004  		// (bryce) hardcode region?
  2005  		return WriteSecret(encoder, AmazonSecret("us-east-1", bucket, id, secret, "", "", endpoint, advancedConfig), opts)
  2006  	default:
  2007  		return errors.Errorf("did not recognize the choice of object-store")
  2008  	}
  2009  }
  2010  
  2011  // AmazonCreds are options that are applicable specifically to Pachd's
  2012  // credentials in an AWS deployment
  2013  type AmazonCreds struct {
  2014  	// Direct credentials. Only applicable if Pachyderm is given its own permanent
  2015  	// AWS credentials
  2016  	ID     string // Access Key ID
  2017  	Secret string // Secret Access Key
  2018  	Token  string // Access token (if using temporary security credentials
  2019  
  2020  	// Vault options (if getting AWS credentials from Vault)
  2021  	VaultAddress string // normally addresses come from env, but don't have vault service name
  2022  	VaultRole    string
  2023  	VaultToken   string
  2024  }
  2025  
  2026  // WriteAmazonAssets writes assets to an amazon backend.
  2027  func WriteAmazonAssets(encoder serde.Encoder, opts *AssetOpts, region string, bucket string, volumeSize int, creds *AmazonCreds, cloudfrontDistro string, advancedConfig *obj.AmazonAdvancedConfiguration) error {
  2028  	if err := WriteAssets(encoder, opts, AmazonBackend, AmazonBackend, volumeSize, ""); err != nil {
  2029  		return err
  2030  	}
  2031  	var secret map[string][]byte
  2032  	if creds == nil {
  2033  		secret = AmazonIAMRoleSecret(region, bucket, cloudfrontDistro, advancedConfig)
  2034  	} else if creds.ID != "" {
  2035  		secret = AmazonSecret(region, bucket, creds.ID, creds.Secret, creds.Token, cloudfrontDistro, "", advancedConfig)
  2036  	} else if creds.VaultAddress != "" {
  2037  		secret = AmazonVaultSecret(region, bucket, creds.VaultAddress, creds.VaultRole, creds.VaultToken, cloudfrontDistro, advancedConfig)
  2038  	}
  2039  	return WriteSecret(encoder, secret, opts)
  2040  }
  2041  
  2042  // WriteGoogleAssets writes assets to a google backend.
  2043  func WriteGoogleAssets(encoder serde.Encoder, opts *AssetOpts, bucket string, cred string, volumeSize int) error {
  2044  	if err := WriteAssets(encoder, opts, GoogleBackend, GoogleBackend, volumeSize, ""); err != nil {
  2045  		return err
  2046  	}
  2047  	return WriteSecret(encoder, GoogleSecret(bucket, cred), opts)
  2048  }
  2049  
  2050  // WriteMicrosoftAssets writes assets to a microsoft backend
  2051  func WriteMicrosoftAssets(encoder serde.Encoder, opts *AssetOpts, container string, id string, secret string, volumeSize int) error {
  2052  	if err := WriteAssets(encoder, opts, MicrosoftBackend, MicrosoftBackend, volumeSize, ""); err != nil {
  2053  		return err
  2054  	}
  2055  	return WriteSecret(encoder, MicrosoftSecret(container, id, secret), opts)
  2056  }
  2057  
  2058  // Images returns a list of all the images that are used by a pachyderm deployment.
  2059  func Images(opts *AssetOpts) []string {
  2060  	return []string{
  2061  		versionedWorkerImage(opts),
  2062  		etcdImage,
  2063  		postgresImage,
  2064  		grpcProxyImage,
  2065  		pauseImage,
  2066  		versionedPachdImage(opts),
  2067  		opts.DashImage,
  2068  	}
  2069  }
  2070  
  2071  func labels(name string) map[string]string {
  2072  	return map[string]string{
  2073  		"app":   name,
  2074  		"suite": suite,
  2075  	}
  2076  }
  2077  
  2078  func objectMeta(name string, labels, annotations map[string]string, namespace string) metav1.ObjectMeta {
  2079  	return metav1.ObjectMeta{
  2080  		Name:        name,
  2081  		Labels:      labels,
  2082  		Annotations: annotations,
  2083  		Namespace:   namespace,
  2084  	}
  2085  }
  2086  
  2087  // AddRegistry switches the registry that an image is targeting, unless registry is blank
  2088  func AddRegistry(registry string, imageName string) string {
  2089  	if registry == "" {
  2090  		return imageName
  2091  	}
  2092  	parts := strings.Split(imageName, "/")
  2093  	if len(parts) == 3 {
  2094  		parts = parts[1:]
  2095  	}
  2096  	return path.Join(registry, parts[0], parts[1])
  2097  }