Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds w3 rm <root-cid> cmd #20

Merged
merged 12 commits into from
Dec 16, 2022
7 changes: 7 additions & 0 deletions bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
addProof,
listProofs,
upload,
remove,
list,
whoami
} from './index.js'
Expand Down Expand Up @@ -48,6 +49,12 @@ cli.command('ls')
.option('--shards', 'Pretty print with shards in output')
.action(list)

cli.command('rm <root-cid>')
.example('rm bafy...')
.describe('Remove an upload from the uploads listing. Pass --shards to delete the actual data if you are sure no other uploads need them')
.option('--shards', 'Remove all shards referenced by the upload from the store. Use with caution and ensure other uploads do not reference the same shards.')
.action(remove)

cli.command('whoami')
.describe('Print information about the current agent.')
.action(whoami)
Expand Down
51 changes: 50 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import fs from 'fs'
import ora from 'ora'
import ora, { oraPromise } from 'ora'
import tree from 'pretty-tree'
import { Readable } from 'stream'
import { CID } from 'multiformats/cid'
import * as DID from '@ipld/dag-ucan/did'
import { CarWriter } from '@ipld/car'
import { getClient, checkPathsExist, filesize, readProof, filesFromPaths } from './lib.js'
Expand Down Expand Up @@ -75,6 +76,54 @@ export async function list (opts) {
console.log('⁂ Try out `w3 up <path to files>` to upload some')
}
}
/**
* @param {string} rootCid
* @param {object} opts
* @param {boolean} [opts.shards]
*/
export async function remove (rootCid, opts) {
let root
try {
root = CID.parse(rootCid.trim())
} catch (err) {
console.error(`Error: ${rootCid} is not a CID`)
}
const client = await getClient()
let upload
try {
upload = await client.capability.upload.remove(root)
} catch (err) {
console.error(`Remove failed: ${err.message ?? err}`)
console.error(err)
process.exit(1)
}
if (!opts.shards) {
return
}
if (!upload) {
return console.log('⁂ upload not found. could not determine shards to remove.')
}
if (!upload.shards || !upload.shards.length) {
return console.log('⁂ no shards to remove.')
}

const { shards } = upload
console.log(`⁂ removing ${shards.length} shard${shards.length === 1 ? '' : 's'}`)

function removeShard (shard) {
return oraPromise(client.capability.store.remove(shard), {
text: `${shard}`,
successText: `${shard} removed`,
failText: `${shard} failed`
})
}

const results = await Promise.allSettled(shards.map(removeShard))

if (results.some(res => res.status === 'rejected')) {
process.exit(1)
}
}

/**
* @param {string} name
Expand Down
96 changes: 96 additions & 0 deletions test/bin.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,102 @@ test('w3 ls', async (t) => {
t.notThrows(() => CID.parse(JSON.parse(list1.stdout).root))
})

test('w3 remove', async t => {
const env = t.context.env.alice

await execa('./bin.js', ['space', 'create'], { env })

const service = mockService({
upload: {
remove: provide(UploadCapabilities.remove, ({ invocation }) => {
const { nb } = invocation.capabilities[0]
return { root: nb.root }
})
}
})
t.context.setService(service)

t.throwsAsync(() => execa('./bin.js', ['rm', 'nope'], { env }), { message: /not a CID/ })

const rm = await execa('./bin.js', ['rm', 'bafybeih2k7ughhfwedltjviunmn3esueijz34snyay77zmsml5w24tqamm'], { env })
t.is(rm.exitCode, 0)
t.is(service.upload.remove.callCount, 1)
t.is(service.store.remove.callCount, 0)
t.is(rm.stdout, '')
})

test('w3 remove - no such upload', async t => {
const env = t.context.env.alice

await execa('./bin.js', ['space', 'create'], { env })

const service = mockService({
upload: {
remove: provide(UploadCapabilities.remove, () => {})
}
})
t.context.setService(service)

const rm = await execa('./bin.js', ['rm', 'bafybeih2k7ughhfwedltjviunmn3esueijz34snyay77zmsml5w24tqamm', '--shards'], { env })
t.is(rm.exitCode, 0)
t.is(rm.stdout, '⁂ upload not found. could not determine shards to remove.')
})

test('w3 remove --shards', async t => {
const env = t.context.env.alice

await execa('./bin.js', ['space', 'create'], { env })

const service = mockService({
store: {
remove: provide(StoreCapabilities.remove, () => {})
},
upload: {
remove: provide(UploadCapabilities.remove, ({ invocation }) => {
const { nb } = invocation.capabilities[0]
return {
root: nb.root,
shards: [
CID.parse('bagbaiera7ciaeifwrn7oo35gxdalocfj23vkvqus2eup27wt2qcxlvta2wya'),
CID.parse('bagbaiera7ciaeifwrn7oo35gxdalocfj23vkvqus2eup27wt2qcxlvta2wya')
]
}
})
}
})
t.context.setService(service)

const rm = await execa('./bin.js', ['rm', 'bafybeih2k7ughhfwedltjviunmn3esueijz34snyay77zmsml5w24tqamm', '--shards'], { env })
t.is(rm.exitCode, 0)
t.is(service.upload.remove.callCount, 1)
t.is(service.store.remove.callCount, 2)
})

test('w3 remove --shards - no shards to remove', async t => {
const env = t.context.env.alice

await execa('./bin.js', ['space', 'create'], { env })

const service = mockService({
store: {
remove: provide(StoreCapabilities.remove, () => {})
},
upload: {
remove: provide(UploadCapabilities.remove, ({ invocation }) => {
const { nb } = invocation.capabilities[0]
return { root: nb.root }
})
}
})
t.context.setService(service)

const rm = await execa('./bin.js', ['rm', 'bafybeih2k7ughhfwedltjviunmn3esueijz34snyay77zmsml5w24tqamm', '--shards'], { env })
t.is(rm.exitCode, 0)
t.is(service.upload.remove.callCount, 1)
t.is(service.store.remove.callCount, 0)
t.is(rm.stdout, '⁂ no shards to remove.')
})

test('w3 delegation create', async t => {
const env = t.context.env.alice

Expand Down