<commit_before>{ Base, secure, signature } = require 'bongo'

KONFIG       = require 'koding-config-manager'
async        = require 'async'
_            = require 'lodash'
konstraints  = require 'konstraints'

teamutils    = require './teamutils'
KodingError  = require '../../error'
KodingLogger = require '../kodinglogger'

MAX_INT      = Math.pow(2, 32) - 1

helpers      = require './helpers'

# Base class for compute related operations
#
module.exports = class ComputeProvider extends Base

  {
    PLANS, PROVIDERS, fetchGroupStackTemplate, revive,
    fetchUsage, checkTemplateUsage
  } = require './computeutils'

  @trait __dirname, '../../traits/protected'

  { permit } = require '../group/permissionset'

  JMachine       = require './machine'
  JWorkspace     = require '../workspace'

  @COUNTER_TYPE  = {
    stacks       : 'member_stacks'
    instances    : 'member_instances'
  }

  @share()

  @set
    permissions           :
      'sudoer'            : []
      'ping machines'     : ['member', 'moderator']
      'list machines'     : ['member', 'moderator']
      'create machines'   : ['member', 'moderator']
      'delete machines'   : ['member', 'moderator']
      'update machines'   : ['member', 'moderator']
      'list own machines' : ['member', 'moderator']
    sharedMethods         :
      static              :
        ping              :
          (signature Object, Function)
        create            :
          (signature Object, Function)
        remove            :
          (signature Object, Function)
        update            :
          (signature Object, Function)
        fetchAvailable    :
          (signature Object, Function)
        fetchUsage        :
          (signature Object, Function)
        fetchProviders    :
          (signature Function)
        createGroupStack  :
          (signature Function)
        updateTeamCounters:
          (signature String, Function)
        fetchSoloMachines :
          (signature Function)


  @providers      = PROVIDERS


  @fetchProviders = secure (client, callback) ->
    callback null, Object.keys _.pickBy PROVIDERS, (x) -> x.supportsStacks


  # pings to requested provider implementation
  #
  # @param {Object} options
  #   generic options for ComputeProvider calls should include
  #   the provider slug as parameter
  #
  # @option options [String] provider provider slug
  #
  # @example api
  #
  #   {
  #     "provider": "aws"
  #   }
  #
  @ping = (client, options, callback) ->

    { provider } = options
    provider.ping client, options, callback


  @ping$ = permit 'ping machines',
    success: revive {
      shouldReviveClient   : yes
      shouldPassCredential : no
    }, @ping


  # creates a JMachine for requested provider with the provided options
  #
  # @param {Object} options
  #   generic options for ComputeProvider calls should include
  #   the provider slug as parameter
  #
  # @option options [String] provider provider slug
  #
  # @return {JMachine} created JMachine instance
  #
  # @example api
  #
  #   {
  #     "provider": "aws",
  #     "label": "My new Machine",
  #     "stack": "ID_OF_TARGET_STACK",
  #     "credential": "ID_OF_CREDENTIAL",
  #     "meta": {
  #       "instance_type": "t2.micro",
  #       "region": "us-east-1",
  #       "image": "ami-XXXX",
  #       "storage_size": 10
  #     }
  #   }
  #
  @create = ->
  @create = revive

    shouldReviveClient       : yes
    shouldReviveProvisioners : yes
    shouldFetchGroupLimit    : yes

  , (client, options, callback) ->

    { provider, stack, label, provisioners, users, generatedFrom } = options
    { r: { group, user, account } } = client

    provider.create client, options, (err, machineData) ->

      return callback err  if err

      { meta, postCreateOptions, credential } = machineData

      label ?= machineData.label

      JMachine.create {
        provider : provider.slug
        label, meta, group, user, generatedFrom
        users, credential, provisioners
      }, (err, machine) ->

        # TODO if any error occurs here which means user paid for
        # not created vm ~ GG
        return callback err  if err

        provider.postCreate client, {
          postCreateOptions, machine, meta, stack: stack._id
        }, (err) ->

          return callback err  if err

          stack.appendTo { machines: machine.getId() }, (err) ->
            callback err, machine


  @create$ = permit 'create machines', { success: revive

    shouldReviveClient   : yes
    shouldPassCredential : yes
    shouldReviveProvider : no
    shouldLockProcess    : yes
    shouldFetchGroupLimit : yes

  , (client, options, callback) ->

    { r: { account, group } } = client
    { stack } = options

    JComputeStack = require '../stack'
    JComputeStack.getStack { account, group, stack }, (err, revivedStack) =>
      return callback err  if err?
      return callback new KodingError 'No such stack'  unless revivedStack

      options.stack = revivedStack

      # Reset it here if someone tries to put users
      # from client side request
      options.users = []

      # Remove generatedFrom option if provided
      delete options.generatedFrom

      @create client, options, callback
  }


  @fetchAvailable = secure revive

    shouldReviveClient   : no
    shouldPassCredential : yes

  , (client, options, callback) ->

    { provider } = options
    provider.fetchAvailable client, options, callback


  @fetchUsage$ = secure (client, options, callback) ->
    ComputeProvider.fetchUsage client, options, callback

  @fetchUsage = revive

    shouldReviveClient   : yes
    shouldPassCredential : yes

  , (client, options, callback) ->

    { slug } = options.provider
    fetchUsage client, { provider: slug }, callback


  @update = secure revive

    shouldReviveClient   : yes
    shouldPassCredential : yes

  , (client, options, callback) ->

    { provider } = options
    provider.update client, options, callback


  @remove = secure revive

    shouldReviveClient   : yes
    shouldPassCredential : yes

  , (client, options, callback) ->

    { provider } = options
    provider.remove client, options, callback


  addGroupAdmin = (group, machineInfo, callback) ->

    # TODO Do we need all admins or only some of them? ~ GG
    # Maybe some of them as admin some of them as user etc.
    group.fetchAdmin (err, admin) ->

      if not err and admin and not admin.getId().equals account.getId()
        admin.fetchUser (err, adminUser) ->
          if not err and adminUser
            machineInfo.users = [{
              id       : adminUser.getId(),
              username : adminUser.username,
              sudo     : yes, owner: yes
            }]
          callback machineInfo
      else
        callback machineInfo


  @generateStackFromTemplate = (data, options, callback) ->

    { account, user, group, template, client } = data

    stackRevision = template.template?.sum or ''

    JComputeStack = require '../stack'

    # To be able to mark a generated stack as group stack
    # we need to iterate over existing stacks defined for group
    # and mark this one as groupStack or not.
    #
    # We eventually remove this one once we remove group stack flow ~ GG
    config = _.clone template.config
    for _template in (group.stackTemplates ? [])
      if template._id.equals _template
        config.groupStack = yes
        break

    # Create a new JComputeStack based on the template details
    # We will then add machines and other information into it.
    JComputeStack.create {
      title         : template.title
      config        : config
      credentials   : template.credentials
      baseStackId   : template._id
      groupSlug     : group.slug
      account, stackRevision
    }, (err, stack) ->

      return callback err  if err

      queue         = []
      results       =
        machines    : []

      # Create JMachine documents based on the data
      # provided in template's machines array ~ GG
      template.machines?.forEach (machineInfo) ->

        queue.push (next) ->

          # Set the stack as newly create JComputeStack
          machineInfo.stack         = stack

          # We are passing the provided credential for the template
          # Provider implementation can override this value like we
          # did in Koding Provider ~ GG
          machineInfo.credential    =
            template.credentials?[machineInfo.provider]?.first

          machineInfo.generatedFrom =
            templateId : template._id
            revision   : stackRevision

          # Inline create helper
          create = (machineInfo) ->
            ComputeProvider.create client, machineInfo, (err, machine) ->
              results.machines.push { err, obj: machine }

              return next()  unless machine

              # Create default workspace for the machine
              JWorkspace.createDefault client, machine.uid, (err) ->

                if err
                  console.log \
                    'Failed to create default workspace', machine.uid, err

                next()

          # This is optional, since for koding group for example
          # we don't want to add our admins into users machines ~ GG
          if options.addGroupAdminToMachines
            addGroupAdmin group, machineInfo, (_machine) -> create _machine
          else
            create machineInfo

      async.series queue, -> callback null, { stack, results }


  # Just takes the limit config and the stack template content generates rules
  # based on the limits then verifies the content based on the rules generated
  @validateTemplateContent = (content, limitConfig, callback) ->

    try
      template = JSON.parse content
    catch
      return callback new KodingError 'Template is not valid'

    teamutils.fetchConstraints limitConfig, (err, rules) ->

      { passed, results } = konstraints template, rules, {}
      return callback new KodingError results.last[1]  unless passed

      return callback null


  # Template checker, this will fetch the stack template from given id,
  # will generate konstraint rules based on the group's limit and finally
  # validate the stack template based on these informations ~ GG
  @validateTemplate = (client, stackTemplateId, group, callback) ->

    limitConfig = helpers.getLimitConfig group

    return callback null  unless limitConfig.limit

    JStackTemplate = require './stacktemplate'
    JStackTemplate.one$ client, { _id: stackTemplateId }, (err, data) =>

      return callback err  if err
      return callback new KodingError 'Stack template not found'  unless data

      @validateTemplateContent data.template.content, limitConfig, callback


  # Takes an array of stack template ids and returns the final result ^^
  @validateTemplates = (client, stackTemplates, group, callback) ->

    queue = []

    stackTemplates.forEach (stackTemplateId) -> queue.push (next) ->
      ComputeProvider.validateTemplate client, stackTemplateId, group, (err) ->
        return callback err  if err
        next()

    async.series queue, -> callback null


  @destroyAccountResources = (account, callback) ->

    skip = (type, next) -> (err) ->
      console.log "[#{type}] Failed to destroy account resource:", err  if err
      next()

    async.series [

      # Remove all machines associated with account
      (next) ->
        JMachine.remove {
          users: { $elemMatch: { id: account.getId(), sudo: yes, owner: yes } }
        }, skip 'JMachine', next

      # Remove all stacks associated with account
      (next) ->
        JComputeStack = require '../stack'
        JComputeStack.remove {
          originId: account.getId()
        }, skip 'JComputeStack', next

      # Remove all stack templates associated with account
      (next) ->
        JStackTemplate = require './stacktemplate'
        JStackTemplate.remove {
          originId: account.getId()
        }, skip 'JStackTemplate', next

      # Remove all credentials associated with account
      (next) ->
        JCredential = require './credential'
        JCredential.remove {
          originId: account.getId()
        }, skip 'JCredential', next

    ], ->

      callback null

<commit_msg>computeprovider: add delete credential data for destroyAccountResources<commit_after>{ Base, secure, signature } = require 'bongo'

KONFIG       = require 'koding-config-manager'
async        = require 'async'
_            = require 'lodash'
konstraints  = require 'konstraints'

teamutils    = require './teamutils'
KodingError  = require '../../error'
KodingLogger = require '../kodinglogger'

MAX_INT      = Math.pow(2, 32) - 1

helpers      = require './helpers'

# Base class for compute related operations
#
module.exports = class ComputeProvider extends Base

  {
    PLANS, PROVIDERS, fetchGroupStackTemplate, revive,
    fetchUsage, checkTemplateUsage
  } = require './computeutils'

  @trait __dirname, '../../traits/protected'

  { permit } = require '../group/permissionset'

  JMachine       = require './machine'
  JWorkspace     = require '../workspace'

  @COUNTER_TYPE  = {
    stacks       : 'member_stacks'
    instances    : 'member_instances'
  }

  @share()

  @set
    permissions           :
      'sudoer'            : []
      'ping machines'     : ['member', 'moderator']
      'list machines'     : ['member', 'moderator']
      'create machines'   : ['member', 'moderator']
      'delete machines'   : ['member', 'moderator']
      'update machines'   : ['member', 'moderator']
      'list own machines' : ['member', 'moderator']
    sharedMethods         :
      static              :
        ping              :
          (signature Object, Function)
        create            :
          (signature Object, Function)
        remove            :
          (signature Object, Function)
        update            :
          (signature Object, Function)
        fetchAvailable    :
          (signature Object, Function)
        fetchUsage        :
          (signature Object, Function)
        fetchProviders    :
          (signature Function)
        createGroupStack  :
          (signature Function)
        updateTeamCounters:
          (signature String, Function)
        fetchSoloMachines :
          (signature Function)


  @providers      = PROVIDERS


  @fetchProviders = secure (client, callback) ->
    callback null, Object.keys _.pickBy PROVIDERS, (x) -> x.supportsStacks


  # pings to requested provider implementation
  #
  # @param {Object} options
  #   generic options for ComputeProvider calls should include
  #   the provider slug as parameter
  #
  # @option options [String] provider provider slug
  #
  # @example api
  #
  #   {
  #     "provider": "aws"
  #   }
  #
  @ping = (client, options, callback) ->

    { provider } = options
    provider.ping client, options, callback


  @ping$ = permit 'ping machines',
    success: revive {
      shouldReviveClient   : yes
      shouldPassCredential : no
    }, @ping


  # creates a JMachine for requested provider with the provided options
  #
  # @param {Object} options
  #   generic options for ComputeProvider calls should include
  #   the provider slug as parameter
  #
  # @option options [String] provider provider slug
  #
  # @return {JMachine} created JMachine instance
  #
  # @example api
  #
  #   {
  #     "provider": "aws",
  #     "label": "My new Machine",
  #     "stack": "ID_OF_TARGET_STACK",
  #     "credential": "ID_OF_CREDENTIAL",
  #     "meta": {
  #       "instance_type": "t2.micro",
  #       "region": "us-east-1",
  #       "image": "ami-XXXX",
  #       "storage_size": 10
  #     }
  #   }
  #
  @create = ->
  @create = revive

    shouldReviveClient       : yes
    shouldReviveProvisioners : yes
    shouldFetchGroupLimit    : yes

  , (client, options, callback) ->

    { provider, stack, label, provisioners, users, generatedFrom } = options
    { r: { group, user, account } } = client

    provider.create client, options, (err, machineData) ->

      return callback err  if err

      { meta, postCreateOptions, credential } = machineData

      label ?= machineData.label

      JMachine.create {
        provider : provider.slug
        label, meta, group, user, generatedFrom
        users, credential, provisioners
      }, (err, machine) ->

        # TODO if any error occurs here which means user paid for
        # not created vm ~ GG
        return callback err  if err

        provider.postCreate client, {
          postCreateOptions, machine, meta, stack: stack._id
        }, (err) ->

          return callback err  if err

          stack.appendTo { machines: machine.getId() }, (err) ->
            callback err, machine


  @create$ = permit 'create machines', { success: revive

    shouldReviveClient   : yes
    shouldPassCredential : yes
    shouldReviveProvider : no
    shouldLockProcess    : yes
    shouldFetchGroupLimit : yes

  , (client, options, callback) ->

    { r: { account, group } } = client
    { stack } = options

    JComputeStack = require '../stack'
    JComputeStack.getStack { account, group, stack }, (err, revivedStack) =>
      return callback err  if err?
      return callback new KodingError 'No such stack'  unless revivedStack

      options.stack = revivedStack

      # Reset it here if someone tries to put users
      # from client side request
      options.users = []

      # Remove generatedFrom option if provided
      delete options.generatedFrom

      @create client, options, callback
  }


  @fetchAvailable = secure revive

    shouldReviveClient   : no
    shouldPassCredential : yes

  , (client, options, callback) ->

    { provider } = options
    provider.fetchAvailable client, options, callback


  @fetchUsage$ = secure (client, options, callback) ->
    ComputeProvider.fetchUsage client, options, callback

  @fetchUsage = revive

    shouldReviveClient   : yes
    shouldPassCredential : yes

  , (client, options, callback) ->

    { slug } = options.provider
    fetchUsage client, { provider: slug }, callback


  @update = secure revive

    shouldReviveClient   : yes
    shouldPassCredential : yes

  , (client, options, callback) ->

    { provider } = options
    provider.update client, options, callback


  @remove = secure revive

    shouldReviveClient   : yes
    shouldPassCredential : yes

  , (client, options, callback) ->

    { provider } = options
    provider.remove client, options, callback


  addGroupAdmin = (group, machineInfo, callback) ->

    # TODO Do we need all admins or only some of them? ~ GG
    # Maybe some of them as admin some of them as user etc.
    group.fetchAdmin (err, admin) ->

      if not err and admin and not admin.getId().equals account.getId()
        admin.fetchUser (err, adminUser) ->
          if not err and adminUser
            machineInfo.users = [{
              id       : adminUser.getId(),
              username : adminUser.username,
              sudo     : yes, owner: yes
            }]
          callback machineInfo
      else
        callback machineInfo


  @generateStackFromTemplate = (data, options, callback) ->

    { account, user, group, template, client } = data

    stackRevision = template.template?.sum or ''

    JComputeStack = require '../stack'

    # To be able to mark a generated stack as group stack
    # we need to iterate over existing stacks defined for group
    # and mark this one as groupStack or not.
    #
    # We eventually remove this one once we remove group stack flow ~ GG
    config = _.clone template.config
    for _template in (group.stackTemplates ? [])
      if template._id.equals _template
        config.groupStack = yes
        break

    # Create a new JComputeStack based on the template details
    # We will then add machines and other information into it.
    JComputeStack.create {
      title         : template.title
      config        : config
      credentials   : template.credentials
      baseStackId   : template._id
      groupSlug     : group.slug
      account, stackRevision
    }, (err, stack) ->

      return callback err  if err

      queue         = []
      results       =
        machines    : []

      # Create JMachine documents based on the data
      # provided in template's machines array ~ GG
      template.machines?.forEach (machineInfo) ->

        queue.push (next) ->

          # Set the stack as newly create JComputeStack
          machineInfo.stack         = stack

          # We are passing the provided credential for the template
          # Provider implementation can override this value like we
          # did in Koding Provider ~ GG
          machineInfo.credential    =
            template.credentials?[machineInfo.provider]?.first

          machineInfo.generatedFrom =
            templateId : template._id
            revision   : stackRevision

          # Inline create helper
          create = (machineInfo) ->
            ComputeProvider.create client, machineInfo, (err, machine) ->
              results.machines.push { err, obj: machine }

              return next()  unless machine

              # Create default workspace for the machine
              JWorkspace.createDefault client, machine.uid, (err) ->

                if err
                  console.log \
                    'Failed to create default workspace', machine.uid, err

                next()

          # This is optional, since for koding group for example
          # we don't want to add our admins into users machines ~ GG
          if options.addGroupAdminToMachines
            addGroupAdmin group, machineInfo, (_machine) -> create _machine
          else
            create machineInfo

      async.series queue, -> callback null, { stack, results }


  # Just takes the limit config and the stack template content generates rules
  # based on the limits then verifies the content based on the rules generated
  @validateTemplateContent = (content, limitConfig, callback) ->

    try
      template = JSON.parse content
    catch
      return callback new KodingError 'Template is not valid'

    teamutils.fetchConstraints limitConfig, (err, rules) ->

      { passed, results } = konstraints template, rules, {}
      return callback new KodingError results.last[1]  unless passed

      return callback null


  # Template checker, this will fetch the stack template from given id,
  # will generate konstraint rules based on the group's limit and finally
  # validate the stack template based on these informations ~ GG
  @validateTemplate = (client, stackTemplateId, group, callback) ->

    limitConfig = helpers.getLimitConfig group

    return callback null  unless limitConfig.limit

    JStackTemplate = require './stacktemplate'
    JStackTemplate.one$ client, { _id: stackTemplateId }, (err, data) =>

      return callback err  if err
      return callback new KodingError 'Stack template not found'  unless data

      @validateTemplateContent data.template.content, limitConfig, callback


  # Takes an array of stack template ids and returns the final result ^^
  @validateTemplates = (client, stackTemplates, group, callback) ->

    queue = []

    stackTemplates.forEach (stackTemplateId) -> queue.push (next) ->
      ComputeProvider.validateTemplate client, stackTemplateId, group, (err) ->
        return callback err  if err
        next()

    async.series queue, -> callback null


  @destroyAccountResources = (account, callback) ->

    skip = (type, next) -> (err) ->
      console.log "[#{type}] Failed to destroy account resource:", err  if err
      next()

    async.series [

      # Remove all machines associated with account
      (next) ->
        JMachine.remove {
          users: { $elemMatch: { id: account.getId(), sudo: yes, owner: yes } }
        }, skip 'JMachine', next

      # Remove all stacks associated with account
      (next) ->
        JComputeStack = require '../stack'
        JComputeStack.remove {
          originId: account.getId()
        }, skip 'JComputeStack', next

      # Remove all stack templates associated with account
      (next) ->
        JStackTemplate = require './stacktemplate'
        JStackTemplate.remove {
          originId: account.getId()
        }, skip 'JStackTemplate', next

      # Remove all credentials associated with account
      (next) ->
        JCredential = require './credential'
        JCredential.remove {
          originId: account.getId()
        }, skip 'JCredential', next

      (next) ->
        JCredentialData = require './credentialdata'
        JCredentialData.remove {
          originId: account.getId()
        }, skip 'JCredentialData', next

    ], ->

      callback null


{'fraction_non_alphanumeric': 0.08869958415761638, 'fraction_numerical': 0.0007267148451693649, 'mean_word_length': 2.2604975648282215, 'pattern_counts': {'":': 20, '<': 3, '<?xml version=': 0, '>': 94, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7489373', 'n_tokens_mistral': 6661, 'n_tokens_neox': 6383, 'n_words': 2142}