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!: provision using a proof #123

Merged
merged 12 commits into from
Nov 16, 2023
29 changes: 27 additions & 2 deletions bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getPkg } from './lib.js'
import {
Account,
Space,
Coupon,
accessClaim,
addSpace,
listSpaces,
Expand All @@ -21,7 +22,8 @@ import {
remove,
list,
whoami,
usageReport
usageReport,
getPlan,
} from './index.js'
import {
storeAdd,
Expand All @@ -30,7 +32,7 @@ import {
uploadAdd,
uploadList,
uploadRemove,
filecoinInfo
filecoinInfo,
} from './can.js'

const pkg = getPkg()
Expand All @@ -52,6 +54,12 @@ cli
)
.action(Account.login)

cli
.command('plan get [email]')
.example('plan get [email protected]')
.describe('Displays plan given account is on')
.action(getPlan)

cli
.command('account ls')
.alias('account list')
Expand Down Expand Up @@ -122,6 +130,8 @@ cli
.command('space provision [name]')
.describe('Associating space with a billing account')
.option('-c, --customer', 'The email address of the billing account')
.option('--coupon', 'Coupon URL to provision space with')
.option('-p, -password', 'Coupon password')
.option(
'-p, --provider',
'The storage provider to associate with this space.'
Expand Down Expand Up @@ -152,6 +162,21 @@ cli
.describe('Set the current space in use by the agent')
.action(useSpace)

cli
.command('coupon create <did>')
.option('--password', 'Password for created coupon.')
.option('-c, --can', 'One or more abilities to delegate.')
.option(
'-e, --expiration',
'Unix timestamp when the delegation is no longer valid. Zero indicates no expiration.',
0
)
.option(
'-o, --output',
'Path of file to write the exported delegation data to.'
)
.action(Coupon.issue)

cli
.command('delegation create <audience-did>')
.describe(
Expand Down
55 changes: 55 additions & 0 deletions coupon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import fs from 'node:fs/promises'
import * as DID from '@ipld/dag-ucan/did'
import * as Account from './account.js'
import * as Space from './space.js'
import { getClient } from './lib.js'
import * as ucanto from '@ucanto/core'

export { Account, Space }

/**
* @typedef {object} CouponIssueOptions
* @property {string} customer
* @property {string[]|string} [can]
* @property {string} [password]
* @property {number} [expiration]
* @property {string} [output]
*
* @param {string} customer
* @param {CouponIssueOptions} options
*/
export const issue = async (
customer,
{ can = 'provider/add', expiration, password, output }
) => {
const client = await getClient()

const audience = DID.parse(customer)
const abilities = can ? [can].flat() : []
if (!abilities.length) {
console.error('Error: missing capabilities for delegation')
process.exit(1)
}

const capabilities = /** @type {ucanto.API.Capabilities} */ (
abilities.map((can) => ({ can, with: audience.did() }))
)

const coupon = await client.coupon.issue({
capabilities,
expiration: expiration === 0 ? Infinity : expiration,
password,
})

const { ok: bytes, error } = await coupon.archive()
if (!bytes) {
console.error(error)
return process.exit(1)
}

if (output) {
await fs.writeFile(output, bytes)
} else {
process.stdout.write(bytes)
}
}
58 changes: 49 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from './lib.js'
import * as ucanto from '@ucanto/core'
import chalk from 'chalk'

export * as Coupon from './coupon.js'
export { Account, Space }

/**
Expand All @@ -31,6 +31,31 @@ export async function accessClaim() {
await client.capability.access.claim()
}

/**
* @param {string} email
*/
export const getPlan = async (email = '') => {
const client = await getClient()
const account =
email === ''
? await Space.selectAccount(client)
: await Space.useAccount(client, { email })

if (account) {
const { ok: plan, error } = await account.plan.get()
if (plan) {
console.log(`⁂ ${plan.product}`)
} else if (error?.name === 'PlanNotFound') {
console.log('⁂ no plan has been selected yet')
} else {
console.error(`Failed to get plan - ${error.message}`)
process.exit(1)
}
} else {
process.exit(1)
}
}

/**
* @param {`${string}@${string}`} email
* @param {object} [opts]
Expand Down Expand Up @@ -95,8 +120,8 @@ export async function upload(firstPath, opts) {
const uploadFn = opts?.car
? client.uploadCAR.bind(client, files[0])
: files.length === 1 && opts?.['no-wrap']
? client.uploadFile.bind(client, files[0])
: client.uploadDirectory.bind(client, files)
? client.uploadFile.bind(client, files[0])
: client.uploadDirectory.bind(client, files)

const root = await uploadFn({
onShardStored: ({ cid, size, piece }) => {
Expand Down Expand Up @@ -492,18 +517,31 @@ export async function usageReport(opts) {
const period = {
// we may not have done a snapshot for this month _yet_, so get report from last month -> now
from: startOfLastMonth(now),
to: now
to: now,
}

let total = 0
for await (const { account, provider, space, size } of getSpaceUsageReports(client, period)) {
for await (const { account, provider, space, size } of getSpaceUsageReports(
client,
period
)) {
if (opts?.json) {
console.log(dagJSON.stringify({ account, provider, space, size, reportedAt: now.toISOString() }))
console.log(
dagJSON.stringify({
account,
provider,
space,
size,
reportedAt: now.toISOString(),
})
)
} else {
console.log(` Account: ${account}`)
console.log(`Provider: ${provider}`)
console.log(` Space: ${space}`)
console.log(` Size: ${opts?.human ? filesize(size.final) : size.final}\n`)
console.log(
` Size: ${opts?.human ? filesize(size.final) : size.final}\n`
)
}
total += size.final
}
Expand All @@ -516,9 +554,11 @@ export async function usageReport(opts) {
* @param {import('@web3-storage/w3up-client').Client} client
* @param {{ from: Date, to: Date }} period
*/
async function * getSpaceUsageReports (client, period) {
async function* getSpaceUsageReports(client, period) {
for (const account of Object.values(client.accounts())) {
const subscriptions = await client.capability.subscription.list(account.did())
const subscriptions = await client.capability.subscription.list(
account.did()
)
for (const { consumers } of subscriptions.results) {
for (const space of consumers) {
const result = await client.capability.usage.report(space, period)
Expand Down
Loading